Skip to content

Commit

Permalink
Environment - improvements II
Browse files Browse the repository at this point in the history
- fixed UI issues - environment dropdowns, colored dots before environment names etc.
- added option "all environments" in the dropdown on the Integration page
- added dropdown with environments for opening the template in the TemplatesForm (integration)
- the Cookies API now supports returning all cookies across environments using the `environment=*` parameter
  • Loading branch information
tg666 committed Oct 13, 2023
1 parent c36c2ab commit 1175504
Show file tree
Hide file tree
Showing 32 changed files with 417 additions and 194 deletions.
17 changes: 11 additions & 6 deletions assets/js/components/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,7 @@ module.exports = () => ({
continue;
}

if (
null === project.currentEnvironment && null === environment
|| (null !== project.currentEnvironment && null !== environment && project.currentEnvironment.code === environment.code)
) {
if (null !== project.currentEnvironment && project.currentEnvironment.code === environment.code) {
continue;
}

Expand Down Expand Up @@ -162,8 +159,12 @@ module.exports = () => ({
query.push(`endDate=${encodeURIComponent(end.format('YYYY-MM-DD'))}`);
query.push(`projects[]=${encodeURIComponent(project.code)}`);

if (null !== project.currentEnvironment) {
query.push(`environment=${encodeURIComponent(project.currentEnvironment.code)}`);
if (project.environments.length) {
if (null !== project.currentEnvironment && null !== project.currentEnvironment.code) {
query.push(`environment=${encodeURIComponent(project.currentEnvironment.code)}`);
}
} else {
query.push('environment=*');
}

const promise = fetch(this.request.endpoint + '?' + query.join('&'), {
Expand Down Expand Up @@ -248,6 +249,10 @@ module.exports = () => ({
throw new Error('Invalid project data, the required keys are "code", "name" and "color".');
}

if (project.environments.length) {
project.currentEnvironment = project.environments[0];
}

this.projects.push(project);
},

Expand Down
2 changes: 1 addition & 1 deletion src/Api/Internal/Controller/StatisticsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ private function buildData(array $projectIdsByCodes, string $locale, DateTimeImm
}

$period = Period::create($startDate, $endDate);
$environmentImpl = '//default//' === $environment ? new DefaultEnvironment() : (is_string($environment) ? new NamedEnvironment($environment) : null);
$environmentImpl = empty($environment) ? new DefaultEnvironment() : ('*' !== $environment ? new NamedEnvironment($environment) : null);

foreach ($projectIdsByCodes as $code => $projectId) {
$consentStatistics = $this->projectStatisticsCalculator->calculateConsentStatistics(
Expand Down
83 changes: 62 additions & 21 deletions src/Api/V1/Controller/CookiesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use App\Application\Cookie\TemplateRendererInterface;
use App\Application\GlobalSettings\EnabledEnvironmentsResolver;
use App\Application\GlobalSettings\GlobalSettingsInterface;
use App\Domain\GlobalSettings\ValueObject\Environment;
use App\Domain\Project\ValueObject\Environments;
use App\Domain\Project\ValueObject\ProjectId;
use App\Domain\Shared\ValueObject\Locale;
Expand All @@ -40,12 +41,17 @@ public function __construct(
private readonly EtagStoreInterface $etagStore,
) {}

public static function getTemplateUrl(string $projectCode, ?string $locale = null): string
public static function getTemplateUrl(string $projectCode, ?string $locale = null, ?string $environment = null): string
{
$query = http_build_query([
'locale' => $locale,
'environment' => $environment,
]);

return sprintf(
'/api/v1/cookies/%s/template%s',
$projectCode,
null !== $locale ? '?locale=' . $locale : '',
'' !== $query ? ('?' . $query) : '',
);
}

Expand Down Expand Up @@ -104,12 +110,13 @@ public function getJson(ApiRequest $request, ApiResponse $response): ApiResponse
$projectCode = $request->getParameter('project');
$requestEntity = $request->getEntity();
assert($requestEntity instanceof CookiesRequestBody);
$environment = empty($requestEntity->environment) ? null : $requestEntity->environment;

$etagKey = sprintf(
'%s/%s/%s/[%s]/json',
$projectCode,
$requestEntity->locale ?? '_',
$requestEntity->environment ?? '__default__',
$environment ?? '#default',
implode(',', (array) $requestEntity->category),
);

Expand All @@ -130,7 +137,7 @@ public function getJson(ApiRequest $request, ApiResponse $response): ApiResponse
]);
}

$errorResponse = $this->tryCreateErrorResponseOnInvalidEnvironment($project->environments, $requestEntity, $response);
$errorResponse = $this->tryCreateErrorResponseOnInvalidEnvironment($project->environments, $environment, $response);

if (null !== $errorResponse) {
return $errorResponse;
Expand All @@ -142,7 +149,13 @@ public function getJson(ApiRequest $request, ApiResponse $response): ApiResponse

$responseBody = json_encode([
'status' => 'success',
'data' => $this->getCookiesData($project->id, $locale, $project->locales->defaultLocale(), $requestEntity->category, $requestEntity->environment),
'data' => $this->getCookiesData(
projectId: $project->id,
locale: $locale,
defaultLocale: $project->locales->defaultLocale(),
environments: $this->createEnvironmentsForQuery($environment, $project->environments),
categories: $requestEntity->category,
),
], JSON_THROW_ON_ERROR);

$response = $response->withStatus(ApiResponse::S200_OK)
Expand All @@ -169,12 +182,13 @@ public function getTemplate(ApiRequest $request, ApiResponse $response): ApiResp
$projectCode = $request->getParameter('project');
$requestEntity = $request->getEntity();
assert($requestEntity instanceof CookiesRequestBody);
$environment = empty($requestEntity->environment) ? null : $requestEntity->environment;

$etagKey = sprintf(
'%s/%s/%s/[%s]/html',
$projectCode,
$requestEntity->locale ?? '_',
$requestEntity->environment ?? '__default__',
$environment ?? '#default',
implode(',', (array) $requestEntity->category),
);

Expand All @@ -195,7 +209,7 @@ public function getTemplate(ApiRequest $request, ApiResponse $response): ApiResp
]);
}

$errorResponse = $this->tryCreateErrorResponseOnInvalidEnvironment($projectTemplate->environments, $requestEntity, $response);
$errorResponse = $this->tryCreateErrorResponseOnInvalidEnvironment($projectTemplate->environments, $environment, $response);

if (null !== $errorResponse) {
return $errorResponse;
Expand All @@ -205,14 +219,20 @@ public function getTemplate(ApiRequest $request, ApiResponse $response): ApiResp
? Locale::fromValue($requestEntity->locale)
: $projectTemplate->projectLocalesConfig->defaultLocale();

$data = $this->getCookiesData($projectTemplate->projectId, $locale, $projectTemplate->projectLocalesConfig->defaultLocale(), $requestEntity->category, $requestEntity->environment);
$data = $this->getCookiesData(
projectId: $projectTemplate->projectId,
locale: $locale,
defaultLocale: $projectTemplate->projectLocalesConfig->defaultLocale(),
environments: $this->createEnvironmentsForQuery($environment, $projectTemplate->environments),
categories: $requestEntity->category,
);
$data = json_encode($data, JSON_THROW_ON_ERROR);
$data = json_decode($data, false, 512, JSON_THROW_ON_ERROR);

$template = Template::create(
$projectTemplate->projectId->toString(),
$projectTemplate->template->value(),
TemplateArguments::create($data->providers, $data->cookies, $requestEntity->environment),
TemplateArguments::create($data->providers, $data->cookies, $environment),
);

$responseBody = $this->templateRenderer->render($template);
Expand Down Expand Up @@ -257,24 +277,25 @@ private function applyCacheHeaders(string $etagKey, string $responseBody, ApiRes
/**
* @param mixed $categories
*/
private function getCookiesData(ProjectId $projectId, Locale $locale, ?Locale $defaultLocale, string|array|null $categories = null, ?string $environment = null): array
private function getCookiesData(ProjectId $projectId, Locale $locale, ?Locale $defaultLocale, array $environments, string|array|null $categories = null): array
{
$data = [
'providers' => [],
'cookies' => [],
];

$query = FindCookiesForApiQuery::create($projectId->toString(), null !== $defaultLocale && $defaultLocale->equals($locale) ? null : $locale->value())
->withBatchSize(100);
$query = FindCookiesForApiQuery::create(
projectId: $projectId->toString(),
environments: $environments,
locale: null !== $defaultLocale && $defaultLocale->equals($locale) ? null : $locale->value(),
);

$query = $query->withBatchSize(100);

if (null !== $categories) {
$query = $query->withCategoryCodes((array) $categories);
}

if (null !== $environment) {
$query = $query->withEnvironment($environment);
}

foreach ($this->queryBus->dispatch($query) as $batch) {
assert($batch instanceof Batch);

Expand Down Expand Up @@ -308,9 +329,29 @@ private function getCookiesData(ProjectId $projectId, Locale $locale, ?Locale $d
return $data;
}

private function tryCreateErrorResponseOnInvalidEnvironment(Environments $projectEnvironments, CookiesRequestBody $body, ApiResponse $response): ?ApiResponse
/**
* @return array<string|null>
*/
private function createEnvironmentsForQuery(?string $environment, Environments $projectEnvironments): array
{
if ('*' !== $environment) {
return [$environment];
}

$environments = array_map(
static fn (Environment $environment): string => $environment->code,
EnabledEnvironmentsResolver::resolveProjectEnvironments(
globalSettingsEnvironments: $this->globalSettings->environments(),
projectEnvironments: $projectEnvironments,
),
);

return [null, ...$environments];
}

private function tryCreateErrorResponseOnInvalidEnvironment(Environments $projectEnvironments, ?string $environment, ApiResponse $response): ?ApiResponse
{
if (null === $body->environment || '' === $body->environment) {
if (null === $environment || '*' === $environment) {
return null;
}

Expand All @@ -319,8 +360,8 @@ private function tryCreateErrorResponseOnInvalidEnvironment(Environments $projec
projectEnvironments: $projectEnvironments,
);

foreach ($environments as $environment) {
if ($environment->code === $body->environment) {
foreach ($environments as $env) {
if ($env->code === $environment) {
return null;
}
}
Expand All @@ -332,7 +373,7 @@ private function tryCreateErrorResponseOnInvalidEnvironment(Environments $projec
'code' => ApiResponse::S400_BAD_REQUEST,
'error' => sprintf(
'Project does not have the "%s" environment.',
$body->environment,
$environment,
),
],
]);
Expand Down
4 changes: 4 additions & 0 deletions src/Domain/GlobalSettings/ValueObject/Environment.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public static function fromNative(mixed $native): self
}
}

if (!preg_match('/^[a-z0-9_\-\.]+$/', $native['code'])) {
throw UnableToCreateEnvironmentFromNativeValue::invalidNativeValueType($key, 'a string that matches the pattern "^[a-z0-9_\-\.]+$".');
}

return new Environment(
code: $native['code'],
name: $native['name'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use App\ReadModel\Cookie\FindCookiesForApiQuery;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr\Join;
use Doctrine\ORM\Query\Expr\Orx;
use Generator;
use SixtyEightPublishers\ArchitectureBundle\Infrastructure\Doctrine\ReadModel\BatchGeneratorFactory;
use SixtyEightPublishers\ArchitectureBundle\ReadModel\Query\QueryHandlerInterface;
Expand All @@ -37,11 +38,20 @@ public function __invoke(FindCookiesForApiQuery $query): Generator
->leftJoin(ProjectHasCookieProvider::class, 'phc', Join::WITH, 'phc.cookieProviderId = cp.id AND phc.project = p')
->where('c.deletedAt IS NULL')
->andWhere('c.active = true')
->andWhere('(c.allEnvironments = true OR JSONB_CONTAINS(c.environments, :environment) = true)')
->andWhere('(phc.id IS NOT NULL OR cp.id = p.cookieProviderId)')
->orderBy('c.createdAt', 'ASC')
->setParameter('projectId', $query->projectId())
->setParameter('environment', json_encode([$query->environment()]));
->setParameter('projectId', $query->projectId());

$environmentsConditions = [
'c.allEnvironments = true',
];

foreach ($query->environments() as $index => $environment) {
$environmentsConditions[] = "JSONB_CONTAINS(c.environments, :environment_$index) = true";
$qb->setParameter('environment_' . $index, json_encode([$environment]));
}

$qb->andWhere(new Orx($environmentsConditions));

$qb->leftJoin('c.translations', 'ct_default', Join::WITH, 'ct_default.locale = p.locales.defaultLocale')
->leftJoin('cat.translations', 'catt_default', Join::WITH, 'catt_default.locale = p.locales.defaultLocale')
Expand Down
18 changes: 10 additions & 8 deletions src/ReadModel/Cookie/FindCookiesForApiQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
*/
final class FindCookiesForApiQuery extends AbstractBatchedQuery
{
public static function create(string $projectId, ?string $locale = null): self
/**
* @param non-empty-list<string|null> $environments
*/
public static function create(string $projectId, array $environments, ?string $locale = null): self
{
return self::fromParameters([
'project_id' => $projectId,
'environments' => $environments,
'locale' => $locale,
]);
}
Expand All @@ -27,11 +31,6 @@ public function withCategoryCodes(array $categoryCodes): self
return $this->withParam('category_codes', $categoryCodes);
}

public function withEnvironment(string $environment): self
{
return $this->withParam('environment', $environment);
}

public function projectId(): string
{
return $this->getParam('project_id');
Expand All @@ -50,8 +49,11 @@ public function categoryCodes(): ?array
return $this->getParam('category_codes');
}

public function environment(): ?string
/**
* @return non-empty-list<string|null>
*/
public function environments(): array
{
return $this->getParam('environment');
return $this->getParam('environments');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ protected function createComponentForm(): Form
$container->addText('code')
->setHtmlAttribute('placeholder', 'code.placeholder')
->setRequired('code.required')
->addRule(Form::Pattern, 'code.rule.pattern', '[a-z0-9_\-\.]+')
->addRule(UniqueMultiplierValuesValidator::Validator, 'code.rule.values_are_not_unique');

$container->addText('name')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<div class="ml-3 text-sm">
{if '' === $k}
<label n:name="$control:$k" class="inline-flex items-center gap-x-1.5 rounded-md px-2 py-0.5">
<svg class="h-1.5 w-1.5 fill-white" viewBox="0 0 6 6" aria-hidden="true">
<svg class="h-1.5 w-1.5 fill-white rounded border border-black" viewBox="0 0 6 6" aria-hidden="true">
<circle cx="3" cy="3" r="3" />
</svg>
{=$control->translate($v)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,10 @@
<div class="min-w-[150px]">
{if true === $item->environments}
<span class="inline-flex items-center gap-x-1.5 rounded-md px-1 py-1 select-all">
<svg class="h-1.5 w-1.5 fill-white" viewBox="0 0 6 6" aria-hidden="true">
<svg class="h-1.5 w-1.5 fill-black" viewBox="0 0 6 6" aria-hidden="true">
<circle cx="3" cy="3" r="3" />
</svg>
{_all_environments}
{_//layout.all_environments}
</span>
{else}
{var $environments = $item->environments}
Expand All @@ -132,7 +132,7 @@
'x-tooltip.interactive.theme.light-border.placement.right.html.raw' => $tooltip
]}

<span n:attr="$attrs">&nbsp;{_and_more_projects, count($environments)}</span>
<span n:attr="$attrs">{_and_more_projects, count($environments)}</span>
{/if}
</div>
{/if}
Expand Down Expand Up @@ -161,22 +161,22 @@
{define #environment-badge, string|null $environment, bool $vertical = false}
{if null === $environment}
<span n:class="$vertical ? 'flex my-1.5' : 'inline-flex', 'items-center gap-x-1.5 rounded-md px-1 py-1 select-all'">
<svg class="h-1.5 w-1.5 fill-white" viewBox="0 0 6 6" aria-hidden="true">
<svg class="h-1.5 w-1.5 fill-white rounded border border-black" viewBox="0 0 6 6" aria-hidden="true">
<circle cx="3" cy="3" r="3" />
</svg>
{_//layout.default_environment}
</span>
{elseif null !== $_globalEnvironments->getByCode($environment)}
{var $globalEnvironment = $_globalEnvironments->getByCode($environment)}
<span n:class="$vertical ? 'flex my-1.5' : 'inline-flex', 'items-center gap-x-1.5 rounded-md px-1 py-1 select-all'">
<svg class="h-1.5 w-1.5 fill-white" viewBox="0 0 6 6" aria-hidden="true" style="fill: {$globalEnvironment->color->value()|noescape}">
<svg class="h-1.5 w-1.5" viewBox="0 0 6 6" aria-hidden="true" style="fill: {$globalEnvironment->color->value()|noescape}">
<circle cx="3" cy="3" r="3" />
</svg>
{$globalEnvironment->name}
</span>
{else}
<span n:class="$vertical ? 'flex my-1.5' : 'inline-flex', 'items-center gap-x-1.5 rounded-md px-1 py-1 select-all'">
<svg class="h-1.5 w-1.5 fill-white" viewBox="0 0 6 6" aria-hidden="true">
<svg class="h-1.5 w-1.5 fill-white rounded border border-black" viewBox="0 0 6 6" aria-hidden="true">
<circle cx="3" cy="3" r="3" />
</svg>
{$environment}
Expand Down
Loading

0 comments on commit 1175504

Please sign in to comment.