Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Organizers tab to integrations #1226

Merged
merged 26 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
1273d5c
Add new tab
Anahkiasen May 24, 2024
079d38a
Merge branch 'main' into feature/PPF-462
Anahkiasen Jun 11, 2024
cca921c
Set Organizers on Integration value object
Anahkiasen Jun 11, 2024
5c533eb
Add new Search Servie method to find organizers
Anahkiasen Jun 11, 2024
e7958a2
Work on fetching organizers
Anahkiasen Jun 11, 2024
1794a2f
Work on layout of Organizers tab
Anahkiasen Jun 11, 2024
ee7b6f0
Tweak layout of organizers
Anahkiasen Jun 14, 2024
db19436
Add translations
Anahkiasen Jun 14, 2024
77e7292
Merge branch 'main' into feature/PPF-462
Anahkiasen Jun 26, 2024
8aa488e
Split organizers by Test/Live
Anahkiasen Jun 27, 2024
a8f1150
Merge branch 'main' into feature/PPF-462
Anahkiasen Jun 27, 2024
b9858e6
Add button translation
Anahkiasen Jun 27, 2024
97a995d
Unify casing
Anahkiasen Jun 27, 2024
7eae351
Linting
Anahkiasen Jun 27, 2024
40a2274
Revert unused integration resource change
Anahkiasen Jun 27, 2024
861d7bb
Linting
Anahkiasen Jun 27, 2024
d2bb60a
Add missing prop
Anahkiasen Jun 27, 2024
8fca50c
Revert "Revert unused integration resource change"
Anahkiasen Jun 27, 2024
65c9581
Merge branch 'main' into feature/PPF-462
Anahkiasen Jun 27, 2024
e731e63
Merge branch 'main' into feature/PPF-462
Anahkiasen Jul 1, 2024
e943b07
Merge remote-tracking branch 'refs/remotes/origin/feature/PPF-462' in…
Anahkiasen Jul 1, 2024
be688f1
Add missing key for search-related tests
Anahkiasen Jul 1, 2024
b4358d7
Fixed integrations listing extra organizers when none are linked
Anahkiasen Jul 2, 2024
fbebbc1
Merge branch 'main' into feature/PPF-462
Anahkiasen Jul 2, 2024
1f5a3c1
Merge branch 'main' into feature/PPF-462
Anahkiasen Jul 4, 2024
60df9df
Update CopyText usage
Anahkiasen Jul 4, 2024
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
2 changes: 1 addition & 1 deletion .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,4 @@ KEYCLOAK_PROD_SCOPE_ENTRY_API_ID='d8a54568-26da-412b-a441-d5e2fad84478' #publiq-
KEYCLOAK_PROD_SCOPE_UITPAS_ID='bcfb28cc-454f-488a-b080-6a29d9c0158e'#uitpas-scope

SEARCH_BASE_URI=https://search-acc.uitdatabank.be/
SEARCH_API_KEY=
SEARCH_API_KEY=deb306a6-6f46-4c98-89ce-b03ec4fd11e2
24 changes: 20 additions & 4 deletions app/Domain/Integrations/Controllers/IntegrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
use App\Domain\Contacts\Repositories\ContactKeyVisibilityRepository;
use App\Domain\Contacts\Repositories\ContactRepository;
use App\Domain\Coupons\Repositories\CouponRepository;
use App\Domain\Integrations\FormRequests\KeyVisibilityUpgradeRequest;
use App\Domain\Integrations\FormRequests\RequestActivationRequest;
use App\Domain\Integrations\FormRequests\StoreContactRequest;
use App\Domain\Integrations\FormRequests\StoreIntegrationRequest;
use App\Domain\Integrations\FormRequests\StoreIntegrationUrlRequest;
use App\Domain\Integrations\FormRequests\KeyVisibilityUpgradeRequest;
use App\Domain\Integrations\FormRequests\UpdateContactInfoRequest;
use App\Domain\Integrations\FormRequests\UpdateIntegrationRequest;
use App\Domain\Integrations\FormRequests\UpdateIntegrationUrlsRequest;
Expand All @@ -23,27 +23,30 @@
use App\Domain\Integrations\IntegrationType;
use App\Domain\Integrations\IntegrationUrl;
use App\Domain\Integrations\KeyVisibility;
use App\Domain\Integrations\Mappers\KeyVisibilityUpgradeMapper;
use App\Domain\Integrations\Mappers\OrganizationMapper;
use App\Domain\Integrations\Mappers\OrganizerMapper;
use App\Domain\Integrations\Mappers\StoreContactMapper;
use App\Domain\Integrations\Mappers\StoreIntegrationMapper;
use App\Domain\Integrations\Mappers\StoreIntegrationUrlMapper;
use App\Domain\Integrations\Mappers\KeyVisibilityUpgradeMapper;
use App\Domain\Integrations\Mappers\UpdateContactInfoMapper;
use App\Domain\Integrations\Mappers\UpdateIntegrationMapper;
use App\Domain\Integrations\Mappers\UpdateIntegrationUrlsMapper;
use App\Domain\Integrations\Organizer;
use App\Domain\Integrations\Repositories\IntegrationRepository;
use App\Domain\Integrations\Repositories\IntegrationUrlRepository;
use App\Domain\Integrations\Repositories\OrganizerRepository;
use App\Domain\KeyVisibilityUpgrades\KeyVisibilityUpgrade;
use App\Domain\KeyVisibilityUpgrades\Repositories\KeyVisibilityUpgradeRepository;
use App\Domain\Organizations\Repositories\OrganizationRepository;
use App\Domain\Subscriptions\Repositories\SubscriptionRepository;
use App\Domain\KeyVisibilityUpgrades\Repositories\KeyVisibilityUpgradeRepository;
use App\Http\Controllers\Controller;
use App\ProjectAanvraag\ProjectAanvraagUrl;
use App\Router\TranslatedRoute;
use App\Search\Sapi3\SearchService;
use App\UiTiDv1\Repositories\UiTiDv1ConsumerRepository;
use Carbon\Carbon;
use CultuurNet\SearchV3\ValueObjects\Organizer as SapiOrganizer;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Http\RedirectResponse;
Expand All @@ -70,6 +73,7 @@ public function __construct(
private readonly Auth0ClientRepository $auth0ClientRepository,
private readonly UiTiDv1ConsumerRepository $uitidV1ConsumerRepository,
private readonly KeyVisibilityUpgradeRepository $keyVisibilityUpgradeRepository,
private readonly SearchService $searchClient,
private readonly CurrentUser $currentUser
) {
}
Expand Down Expand Up @@ -170,12 +174,24 @@ public function show(string $id): Response
$integration = $this->integrationRepository->getById(Uuid::fromString($id));
$oldCredentialsExpirationDate = $this->getExpirationDateForOldCredentials($integration->getKeyVisibilityUpgrade());

$organizerIds = collect($integration->organizers())->map(fn (Organizer $organizer) => $organizer->organizerId->toString());
$uitpasOrganizers = $this->searchClient->findUiTPASOrganizers(...$organizerIds)->getMember()?->getItems();
$organizers = collect($uitpasOrganizers)->map(function (SapiOrganizer $organizer) {
$id = explode('/', $organizer->getId() ?? '');

return [
'id' => $id[count($id) - 1],
'name' => $organizer->getName()?->getValues() ?? $id,
'status' => $organizer->getWorkflowStatus() === 'ACTIVE' ? 'Live' : 'Test',
];
});
Anahkiasen marked this conversation as resolved.
Show resolved Hide resolved

return Inertia::render('Integrations/Detail', [
'integration' => $integration->toArray(),
'oldCredentialsExpirationDate' => $oldCredentialsExpirationDate,
'email' => Auth::user()?->email,
'subscriptions' => $this->subscriptionRepository->all(),
'organizers' => session('organizers'),
'organizers' => $organizers,
]);
}

Expand Down
20 changes: 20 additions & 0 deletions app/Domain/Integrations/Integration.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ final class Integration

private ?Organization $organization;

/** @var array<Organizer> */
private array $organizers;

/** @var array<UiTiDv1Consumer> */
private array $uiTiDv1Consumers;

Expand All @@ -57,6 +60,7 @@ public function __construct(
$this->uiTiDv1Consumers = [];
$this->auth0Clients = [];
$this->keycloakClients = [];
$this->organizers = [];
$this->organization = null;
$this->keyVisibility = KeyVisibility::v2;
$this->keyVisibilityUpgrade = null;
Expand Down Expand Up @@ -122,6 +126,13 @@ public function withKeycloakClients(KeycloakClient ...$keycloakClients): self
return $clone;
}

public function withOrganizers(Organizer ...$organizers): self
{
$clone = clone $this;
$clone->organizers = $organizers;
return $clone;
}

public function withSubscription(Subscription $subscription): self
{
$clone = clone $this;
Expand Down Expand Up @@ -166,6 +177,14 @@ public function organization(): ?Organization
return $this->organization;
}

/**
* @return array<Organizer>
*/
public function organizers(): array
{
return $this->organizers;
}

/** @return array<UiTiDv1Consumer> */
public function uiTiDv1Consumers(): array
{
Expand Down Expand Up @@ -226,6 +245,7 @@ public function toArray(): array
'contacts' => $this->contacts,
'urls' => $this->urls,
'organization' => $this->organization,
'organizers' => $this->organizers,
'authClients' => $this->auth0Clients,
'legacyAuthConsumers' => $this->uiTiDv1Consumers,
'keycloakClients' => $this->keycloakClients,
Expand Down
5 changes: 5 additions & 0 deletions app/Domain/Integrations/Models/IntegrationModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,11 @@ public function toDomain(): Integration
->get()
->map(fn (KeycloakClientModel $keycloakClientModel) => $keycloakClientModel->toDomain())
->toArray()
)->withOrganizers(
...$this->organizers()
->get()
->map(fn (OrganizerModel $organizerModel) => $organizerModel->toDomain())
->toArray()
);

if ($this->keyVisibilityUpgrade) {
Expand Down
16 changes: 16 additions & 0 deletions app/Search/Sapi3/Sapi3SearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Search\Sapi3;

use CultuurNet\SearchV3\Parameter\Id;
use CultuurNet\SearchV3\Parameter\Query;
use CultuurNet\SearchV3\SearchClientInterface;
use CultuurNet\SearchV3\SearchQuery;
Expand All @@ -25,4 +26,19 @@ public function searchUiTPASOrganizer(string $name): PagedCollection

return $this->searchClient->searchOrganizers($searchQuery);
}

public function findUiTPASOrganizers(string ...$ids): PagedCollection
{
$searchQuery = new SearchQuery();
$searchQuery->setEmbed(true);
if (empty($ids)) {
return new PagedCollection();
}

foreach ($ids as $id) {
$searchQuery->addParameter(new Id($id));
}

return $this->searchClient->searchOrganizers($searchQuery);
}
}
1 change: 1 addition & 0 deletions app/Search/Sapi3/SearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
interface SearchService
{
public function searchUiTPASOrganizer(string $name): PagedCollection;
public function findUiTPASOrganizers(string ...$ids): PagedCollection;
}
5 changes: 5 additions & 0 deletions resources/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@
"description": "Contacts can edit, activate, and delete the integration. Once deleted, a contact no longer has access to the integration."
}
},
"organizers_info": {
"title": "Organizers",
"description": "Below you see an overview of the UiTdatabank organizers for which you can execute actions in the UiTPAS API.",
"add": "Add organizer"
},
"billing_info": {
"title": {
"billing": "Billing information",
Expand Down
5 changes: 5 additions & 0 deletions resources/translations/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,11 @@
"description": "Contacten kunnen de integratie aanpassen, activeren en verwijderen. Eens verwijderd heeft een contact geen toegang meer tot de integratie."
}
},
"organizers_info": {
"title": "Organisaties",
"description": "Hieronder vind je een overzicht van de UiTdatabank organisaties waarvoor je acties kan uitvoeren in de UiTPAS API.",
"add": "Organisatie toevoegen"
},
"billing_info": {
"title": {
"billing": "Facturatiegegevens",
Expand Down
2 changes: 1 addition & 1 deletion resources/ts/Components/CopyText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const CopyText = ({ text, isSecret }: Props) => {
};

return (
<div className="inline-flex self-start gap-2 items-center bg-[#fdf3ef] rounded px-3 p-1">
<div className="inline-flex gap-2 items-center bg-[#fdf3ef] rounded px-3 p-1">
<span
className="font-mono whitespace-pre text-ellipsis overflow-hidden text-sm text-publiq-orange max-md:max-w-[15rem] max-xl:max-w-[30rem]"
ref={codeFieldRef}
Expand Down
73 changes: 73 additions & 0 deletions resources/ts/Components/Integrations/Detail/OrganizersInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from "react";
import { Heading } from "../../Heading";
import { useTranslation } from "react-i18next";
import type { Integration } from "../../../types/Integration";
import { Card } from "../../Card";
import { CopyText } from "../../CopyText";
import { ButtonIcon } from "../../ButtonIcon";
import { faPencil, faTrash } from "@fortawesome/free-solid-svg-icons";
import type { Organizer } from "../../../types/Organizer";
import { groupBy } from "lodash";
import { ButtonPrimary } from "../../ButtonPrimary";

type Props = Integration & { organizers: Organizer[] };

const OrganizersSection = ({
sectionName,
organizers,
}: {
organizers: Organizer[];
sectionName: Organizer["status"];
}) => {
const { t, i18n } = useTranslation();
if (!organizers?.length) {
return null;
}

return (
<>
<Heading level={4} className="font-semibold">
{sectionName}
</Heading>
{organizers.map((organizer) => (
<Card key={organizer.id}>
<div className="grid grid-cols-[1fr,2fr,auto] gap-x-4 items-center">
<h1 className={"font-bold"}>{organizer.name[i18n.language]}</h1>
<div>
<CopyText text={organizer.id} />
</div>
{sectionName === "Live" && (
<div>
<ButtonIcon icon={faPencil} className="text-icon-gray" />
<ButtonIcon icon={faTrash} className="text-icon-gray" />
</div>
)}
</div>
</Card>
))}
<div className="grid lg:grid-cols-3">
{sectionName === "Live" && (
<ButtonPrimary className="col-span-1">
{t("details.organizers_info.add")}
</ButtonPrimary>
)}
</div>
</>
);
};

export const OrganizersInfo = ({ organizers }: Props) => {
const { t } = useTranslation();
const byStatus = groupBy(organizers, "status");

return (
<>
<Heading level={4} className="font-semibold">
{t("details.organizers_info.title")}
</Heading>
<p>{t("details.organizers_info.description")}</p>
<OrganizersSection sectionName="Test" organizers={byStatus["Test"]} />
<OrganizersSection sectionName="Live" organizers={byStatus["Live"]} />
</>
);
};
10 changes: 10 additions & 0 deletions resources/ts/Pages/Integrations/Detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,24 @@ import { PricingPlanProvider } from "../../Context/PricingPlan";
import { useIsMobile } from "../../hooks/useIsMobile";
import { CouponInfoProvider } from "../../Context/CouponInfo";
import type { Integration } from "../../types/Integration";
import { OrganizersInfo } from "../../Components/Integrations/Detail/OrganizersInfo";
import type { Organizer } from "../../types/Organizer";

type Props = {
integration: Integration;
email: string;
subscriptions: Subscription[];
oldCredentialsExpirationDate: string;
errors: Record<string, string | undefined>;
organizers: Organizer[];
};

const Detail = ({
integration,
email,
subscriptions,
oldCredentialsExpirationDate,
organizers,
errors,
}: Props) => {
const { t } = useTranslation();
Expand Down Expand Up @@ -175,6 +179,12 @@ const Detail = ({
duplicateContactErrorMessage={duplicateContactErrorMessage}
/>
</Tabs.Item>
<Tabs.Item
type="organisations"
label={t("details.organizers_info.title")}
>
<OrganizersInfo {...integration} organizers={organizers} />
</Tabs.Item>
<Tabs.Item
type="billing"
label={t("details.billing_info.title.billing")}
Expand Down
6 changes: 6 additions & 0 deletions resources/ts/types/Organizer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export type Organizer = {
id: string;
name: { [key: string]: string };
description: string;
status: "Live" | "Test";
};
Loading