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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,16 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
usage and limit only when it refuses, so a row could not tell "near the limit"
from "nothing measured".

- The governance link on a use-case pack page now carries the pack's
recommended profile.

The page names the recommendation and then invites the operator to compare it
against what is in force. The link went to the readout with nothing selected,
so the profile had to be found again by hand, one screen after reading its
name. The readout has always honoured a `profile` query parameter; only the
link never sent one.


## [0.29.1] - 2026-08-13

### Fixed
Expand Down
15 changes: 12 additions & 3 deletions Classes/Controller/Backend/UseCasePackController.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,13 @@ public function showAction(string $pack = ''): ResponseInterface
'pack' => $useCasePack,
'plan' => $this->installer->plan($useCasePack),
'wizardUrl' => $this->wizardUrl(),
'governanceUrl' => $this->routeUrl('nrllm_overview', 'Backend\\LlmModule', 'governance'),
// Carries the pack's recommended profile as the readout's `profile`
// query parameter, so the comparison the hint asks for is one click
// rather than a hunt through the profile buttons. The readout has
// always honoured it; only the link never sent it.
'governanceUrl' => $this->routeUrl('nrllm_overview', 'Backend\\LlmModule', 'governance', [
'profile' => $useCasePack->recommendedGovernanceProfile->value,
]),
'toolsUrl' => $this->routeUrl('nrllm_tools', 'Backend\\Tool', 'list'),
// The Editor Action Center lives in the editor-facing module
// (ADR-158), not in the admin tree. Linked because the plan names
Expand Down Expand Up @@ -206,12 +212,15 @@ private function wizardUrl(): string
return $this->routeUrl('nrllm_wizard', 'Backend\\SetupWizard', 'index');
}

private function routeUrl(string $route, string $controller, string $action): string
/**
* @param array<string, string> $extra additional query parameters
*/
private function routeUrl(string $route, string $controller, string $action, array $extra = []): string
{
return (string)$this->backendUriBuilder->buildUriFromRoute($route, [
'controller' => $controller,
'action' => $action,
]);
] + $extra);
}

private function enqueueFlashMessage(string $message, string $title, ContextualFeedbackSeverity $severity): void
Expand Down
60 changes: 60 additions & 0 deletions Tests/Functional/Controller/Backend/UseCasePackRenderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
use Netresearch\NrLlm\Tests\Functional\AbstractFunctionalTestCase;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\CMS\Backend\Routing\Route;
use TYPO3\CMS\Core\Core\SystemEnvironmentBuilder;
use TYPO3\CMS\Core\Http\NormalizedParams;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\CMS\Core\Localization\LanguageServiceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\View\ViewFactoryData;
use TYPO3\CMS\Core\View\ViewFactoryInterface;
Expand Down Expand Up @@ -369,6 +371,64 @@ private function render(string $template, string $action, array $variables): str
return $view->render();
}

#[Test]
public function theGovernanceLinkCarriesThePacksRecommendedProfile(): void
{
// The whole point of the link: the operator has just read the
// recommendation's name and wants it compared against what is in force.
// Asserted through a dispatched action rather than by handing the URL to
// the template, which would only prove the template prints what it is
// given (#778).
$this->importFixture('BeUsers.csv');
$this->setUpBackendUser(1);

// The request has to exist before the controller is resolved: Extbase's
// ConfigurationManager captures the ambient one when it is constructed,
// which is what theControllerIsRegisteredInTheContainer() already works
// around.
$request = $this->actionRequest('show', ['pack' => 'editorial-starter']);
$controller = $this->getService(UseCasePackController::class);
$body = (string)$controller->processRequest($request)->getBody();

self::assertStringContainsString('profile=controlled-cloud', $body);
}

/**
* A request for one action of the real controller, with its arguments.
*
* The other tests here hand variables to a view, which can only prove the
* template prints what it is given. A URL the controller builds needs the
* controller to have built it.
*
* @param array<string, string> $arguments
*/
private function actionRequest(string $action, array $arguments = []): ExtbaseRequest
{
$parameters = new ExtbaseRequestParameters();
$parameters->setControllerName('Backend\\UseCasePack');
$parameters->setControllerActionName($action);
$parameters->setControllerExtensionName('NrLlm');
foreach ($arguments as $name => $value) {
$parameters->setArgument($name, $value);
}

// BackendViewFactory resolves its template paths from the route's
// packageName, so a module action cannot be dispatched without one.
$route = new Route('/module/nrllm/usecase', ['packageName' => 'netresearch/nr-llm']);

$serverRequest = (new ServerRequest('https://typo3-testing.local/typo3/', 'GET'))
->withAttribute('applicationType', SystemEnvironmentBuilder::REQUESTTYPE_BE)
->withAttribute('route', $route)
->withAttribute('extbase', $parameters);
$serverRequest = $serverRequest->withAttribute('normalizedParams', NormalizedParams::createFromRequest($serverRequest));
$GLOBALS['TYPO3_REQUEST'] = $serverRequest;
// ModuleTemplate translates its own chrome and reads $GLOBALS['LANG'].
$GLOBALS['LANG'] = $this->getService(LanguageServiceFactory::class)
->createFromUserPreferences($GLOBALS['BE_USER'] ?? null);

return new ExtbaseRequest($serverRequest);
}

/**
* The Module layout renders f:flashMessages, which resolves its queue from
* an extbase request. A plain PSR-7 request makes the LAYOUT fail before
Expand Down
Loading