Skip to content

CFM-474_Pipeline_Toolkit_problems #343

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class IdentifierMappersTable extends Table {
use \App\Lib\Traits\LabeledLogTrait;
use \App\Lib\Traits\PermissionsTrait;
use \App\Lib\Traits\PrimaryLinkTrait;
use \App\Lib\Traits\QueryModificationTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;

Expand Down Expand Up @@ -68,6 +69,19 @@ public function initialize(array $config): void {
$this->setPrimaryLink(['flange_id']);
$this->setRequiresCO(true);

$this->setViewContains([
'Flanges',
]);

$this->setEditContains([
'Flanges',
]);

// Required for deep Breadcrumb calculations
$this->setIndexContains([
'Flanges',
]);

$this->setPermissions([
// Actions that operate over an entity (ie: require an $id)
'entity' => [
Expand Down Expand Up @@ -135,6 +149,17 @@ public function buildRelatedAttributes(
return $retdata;
}

/**
* Table specific logic to generate a display field.
*
* @since COmanage Registry v5.2.0
* @param \PipelineToolkit\Model\Entity\IdentifierMapper $entity Entity to generate display field for
* @return string Display field
*/
public function generateDisplayField(\PipelineToolkit\Model\Entity\IdentifierMapper $entity): string {
return $entity->name ?? $entity->description ?? $entity->flange->description;
}

/**
* Set validation rules.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class LoginIdentifierTypesTable extends Table {
use \App\Lib\Traits\LabeledLogTrait;
use \App\Lib\Traits\PermissionsTrait;
use \App\Lib\Traits\PrimaryLinkTrait;
use \App\Lib\Traits\QueryModificationTrait;
use \App\Lib\Traits\TableMetaTrait;
use \App\Lib\Traits\ValidationTrait;

Expand Down Expand Up @@ -75,6 +76,14 @@ public function initialize(array $config): void {
]
]);

$this->setViewContains([
'IdentifierMappers' => ['Flanges'],
]);

$this->setEditContains([
'IdentifierMappers' => ['Flanges'],
]);

$this->setPermissions([
// Actions that operate over an entity (ie: require an $id)
'entity' => [
Expand Down Expand Up @@ -109,6 +118,17 @@ public function buildRules(RulesChecker $rules): RulesChecker {
return $rules;
}

/**
* Table specific logic to generate a display field.
*
* @since COmanage Registry v5.2.0
* @param \PipelineToolkit\Model\Entity\LoginIdentifierType $entity Entity to generate display field for
* @return string Display field
*/
public function generateDisplayField(\PipelineToolkit\Model\Entity\LoginIdentifierType $entity): string {
return __d('pipeline_toolkit', 'controller.LoginIdentifierTypes', [1]);
}

/**
* Application Rule to determine if a specific Type is already configured.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,8 @@ protected function getChanges(
* @return string Display field
*/
public function generateDisplayField(\SqlConnector\Model\Entity\SqlSource $entity): string {
return __d('sql_connector', 'display.SqlSource', [$entity->external_identity_source->description]);
$value = $entity->name ?? $entity->description ?? $entity->external_identity_source->description;
return __d('sql_connector', 'display.SqlSource', [$value]);
}


Expand Down
3 changes: 2 additions & 1 deletion app/plugins/EnvSource/src/Model/Table/EnvSourcesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,8 @@ public function initialize(array $config): void {
* @return string Display field
*/
public function generateDisplayField(\EnvSource\Model\Entity\EnvSource $entity): string {
return __d('env_source', 'display.EnvSource', [$entity->external_identity_source->description]);
$value = $entity->name ?? $entity->description ?? $entity->external_identity_source->description;
return __d('env_source', 'display.EnvSource', [$value]);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public function initialize(array $config): void {
* @return string Display field
*/
public function generateDisplayField(\SshKeyAuthenticator\Model\Entity\SshKeyAuthenticator $entity): string {
return $entity->authenticator->description;
return $entity->name ?? $entity->description ?? $entity->authenticator->description;
}

/**
Expand Down
98 changes: 90 additions & 8 deletions app/src/Controller/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@
use App\Lib\Util\StringUtilities;
use Cake\Controller\Controller;
use Cake\Datasource\Exception\RecordNotFoundException;
use Cake\Http\Exception\UnauthorizedException;
use Cake\Event\EventManager;
use Cake\Http\Exception\UnauthorizedException;
use Cake\ORM\TableRegistry;
use Cake\Routing\Router;
use Cake\Utility\Hash;


/**
* @property \App\Controller\Component\RegistryAuthComponent $RegistryAuth
* @property \App\Controller\Component\BreadcrumbComponent $Breadcrumb
Expand Down Expand Up @@ -273,16 +273,98 @@ public function getCOID(): ?int {
*/
public function getCurrentTable(): \Cake\ORM\Table
{
/** @var string $modelsName */
$modelsName = $this->getName();
return $this->fetchTable($this->getQualifiedName());
}

/**
* Get the fully-qualified controller/model name with plugin prefix when present.
*
* Examples:
* - Core controller "Users" => "Users"
* - Plugin controller "MyPlugin.Users" => "MyPlugin.Users"
*
* @since COmanage Registry v5.2.0
* @return string
*/
public function getQualifiedName(): string
{
$name = $this->getName();
$plugin = $this->getPlugin();

return $plugin !== null && $plugin !== ''
? $plugin . '.' . $name
: $name;
}

$alias = $this->getPlugin() !== null
? $this->getPlugin() . '.' . $modelsName
: $modelsName;

return $this->fetchTable($alias);
/**
* Check if a given route/URL target is valid and can be resolved.
*
* @param array $target Route parameters to validate
* @return bool True if route is valid and can be resolved
* @since COmanage Registry v5.2.0
*/
public function isValidRoute(array $target): bool
{
try {
// Normalize plugin nulls and query args
$params = $target;
if (array_key_exists('plugin', $params) && $params['plugin'] === null) {
unset($params['plugin']);
}
if (isset($params['?'])) {
$params['query'] = $params['?'];
unset($params['?']);
}

Router::url($params, true); // will throw if unresolvable
return true;
} catch (\Throwable $e) {
return false;
}
}

/**
* Check if target corresponds to an active application route (is routable).
*
* @param array $target e.g. ['plugin'=>'PipelineToolkit','controller'=>'IdentifierMappers','action'=>'index','?'=>['flange_id'=>3]]
* @return bool
* @since COmanage Registry v5.2.0
*/
public function isActiveRoute(array $target): bool
{
try {
// Normalize params for Router::match
$params = $target;

// Query params should not affect route matching
if (isset($params['?'])) {
unset($params['?']);
}
if (isset($params['query'])) {
unset($params['query']);
}

// Default request method and extension are not required for match()
// Build URL first to ensure consistency (optional)
Router::url($target, false);

// Router::match expects a request-style array; provide defaults
$params += [
'plugin' => $params['plugin'] ?? null,
'prefix' => $params['prefix'] ?? null,
'controller' => $params['controller'] ?? null,
'action' => $params['action'] ?? null,
'pass' => array_values(array_filter($params, 'is_int', ARRAY_FILTER_USE_KEY)) ?: [],
];

// If a matching route exists, match() returns an array; otherwise it throws
Router::getRouteCollection()->match($params, []);
return true;
} catch (\Throwable $e) {
return false;
}
}

/**
* @param string $potentialPrimaryLink
Expand Down
102 changes: 77 additions & 25 deletions app/src/Controller/Component/BreadcrumbComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@

use \Cake\Controller\Component;
use \Cake\Event\EventInterface;
use \Cake\ORM\TableRegistry;
use \Cake\Utility\Inflector;
use \Cake\Routing\Router;
use \App\Lib\Util\StringUtilities;

class BreadcrumbComponent extends Component {
Expand All @@ -57,6 +56,9 @@ class BreadcrumbComponent extends Component {
protected $skipConfigPaths = [];
// Don't render the parent links
protected $skipParentPaths = [];
// Skip target index/entity links for paths where the target has no view
protected $skipNoViewPaths = [];

// Inject parent links (these render before the index link, if set)
// The parent links are constructed as part of the injectPrimaryLink function, as well as in the StandardController and
// in the StandardPluginController, MVEAController, ProvisioningHistoryRecordController, etc. These controllers are
Expand Down Expand Up @@ -250,21 +252,41 @@ public function injectPrimaryLink(object $link, bool $index = true, string $link
if ($index && method_exists($linkTable, 'findPrimaryLink')) {
$parentLink = $linkTable->findPrimaryLink($linkedEntity->id);

$this->injectParents[strtolower($linkModelFqn) . ':index'] = [
'target' => [
'plugin' => $parentLink->plugin ?? StringUtilities::blankToNull(StringUtilities::pluginPlugin($linkModelFqn)) ?? null,
'controller' => StringUtilities::pluginModel($linkModelFqn),
'action' => 'index',
'?' => [
$parentLink->attr => $parentLink->value
]
],
'label' => StringUtilities::localizeController(
controllerName: $linkModelFqn,
pluginName: $link->plugin ?? null,
plural: true
)
$targetIndexRoute = [
'plugin' => $parentLink->plugin ?? StringUtilities::blankToNull(StringUtilities::pluginPlugin($linkModelFqn)) ?? null,
'controller' => StringUtilities::pluginModel($linkModelFqn),
'action' => 'index',
'?' => [
$parentLink->attr => $parentLink->value
]
];

// Build absolute URL string for regex matching against injected route
$targetIndexUrl = Router::url($targetIndexRoute, false);

// If the injected route matches any configured "no view" patterns, skip injecting it
$skipInjectedIndex = false;
foreach ($this->skipNoViewPaths as $pattern) {
if (preg_match($pattern, $targetIndexUrl)) {
$skipInjectedIndex = true;
break;
}
}

if($controller->isValidRoute($targetIndexRoute) && $controller->isActiveRoute($targetIndexRoute)) {
if(!$skipInjectedIndex) {
$this->injectParents[strtolower($linkModelFqn) . ':index'] = [
'target' => $targetIndexRoute,
'label' => StringUtilities::localizeController(
controllerName: $linkModelFqn,
pluginName: $link->plugin ?? null,
plural: true
)
];
}
} else {
$this->llog('debug', "Breadcrumbs: invalid route for $linkModelFqn {$parentLink->attr}={$parentLink->value}");
}
}

// Determine target action for entity link
Expand All @@ -279,16 +301,34 @@ public function injectPrimaryLink(object $link, bool $index = true, string $link

$title = StringUtilities::stripActionPrefix($title);

// Inject the entity breadcrumb (unique per table:id)
$this->injectParents[$this->composeEntityKey($linkTable->getTable(), (int)$linkedEntity->id)] = [
'target' => [
'plugin' => $parentLink->plugin ?? StringUtilities::blankToNull(StringUtilities::pluginPlugin($linkModelFqn)) ?? null,
'controller' => StringUtilities::pluginModel($linkModelFqn),
'action' => $breadcrumbAction,
(int)$linkedEntity->id
],
'label' => $linkLabel ?? $title,
$targetRoute = [
'plugin' => $parentLink->plugin ?? StringUtilities::blankToNull(StringUtilities::pluginPlugin($linkModelFqn)) ?? null,
'controller' => StringUtilities::pluginModel($linkModelFqn),
'action' => $breadcrumbAction,
(int)$linkedEntity->id
];

// Build absolute URL for entity link and check skip
$entityUrl = Router::url($targetRoute, false);
$skipInjectedEntity = false;
foreach ($this->skipNoViewPaths as $pattern) {
if (preg_match($pattern, $entityUrl)) {
$skipInjectedEntity = true;
break;
}
}

if($controller->isValidRoute($targetIndexRoute) && $controller->isActiveRoute($targetIndexRoute)) {
if(!$skipInjectedEntity) {
// Inject the entity breadcrumb (unique per table:id)
$this->injectParents[$this->composeEntityKey($linkTable->getTable(), (int)$linkedEntity->id)] = [
'target' => $targetRoute,
'label' => $linkLabel ?? $title,
];
}
} else {
$this->llog('debug', "Breadcrumbs: invalid route for $linkModelFqn {$parentLink->attr}={$parentLink->value}");
}
}
catch (\Cake\Datasource\Exception\RecordNotFoundException $e) {
$this->llog('error', "Breadcrumbs: linked entity not found for $linkModelFqn {$link->attr}={$link->value}");
Expand Down Expand Up @@ -369,6 +409,18 @@ public function skipParents(array $skipPaths): void
$this->skipParentPaths = $skipPaths;
}

/**
* Set the set of paths where the target entity has no dedicated view.
* For these paths, skip generating index/entity breadcrumb links to non-existent views.
*
* @since COmanage Registry v5.2.0
* @param array $skipPaths Array of regular expressions describing paths
*/
public function skipTargetsWithoutView(array $skipPaths): void
{
$this->skipNoViewPaths = $skipPaths;
}

/**
* Resolves the contain list for a given table and mapped action
*
Expand Down
4 changes: 2 additions & 2 deletions app/src/Controller/StandardController.php
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ protected function getRequiredFields() {

public function index() {
/** var string $modelsName */
$modelsName = $this->getName();
$modelsName = $this->getQualifiedName();
/** var Cake\ORM\Table $table */
$table = $this->getCurrentTable();
// $tableName = models
Expand Down Expand Up @@ -671,7 +671,7 @@ public function index() {
$this->set('vv_permission_set', $this->RegistryAuth->calculatePermissionsForResultSet($resultSet));

// Default index view title is model name
[$title, , ] = StringUtilities::entityAndActionToTitle($resultSet, $modelsName, 'index');
[$title, , ] = StringUtilities::entityAndActionToTitle(null, $modelsName, 'index');
$this->set('vv_title', $title);

// Let the view render
Expand Down
Loading