Skip to content

Commit

Permalink
Notifications table
Browse files Browse the repository at this point in the history
  • Loading branch information
Ioannis committed Nov 11, 2025
1 parent 28f3359 commit 0d02d9d
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 3 deletions.
21 changes: 21 additions & 0 deletions app/plugins/Transmogrify/config/schema/tables.json
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,27 @@
},
"postTable": "migrateExtendedAttributesToAdHocAttributes"
},
"notifications": {
"source": "cm_co_notifications",
"displayField": "id",
"addChangelog": true,
"fieldMap": {
"subject_co_person_id": "subject_person_id",
"subject_co_group_id": "subject_group_id",
"actor_co_person_id": "actor_person_id",
"recipient_co_person_id": "recipient_person_id",
"recipient_co_group_id": "recipient_group_id",
"resolver_co_person_id": "resolver_person_id",
"action": "&mapNotificationAction",
"source_url": "source",
"source_controller": null,
"source_action": null,
"source_id": null,
"source_arg0": null,
"source_val0": null,
"email_body": "email_body_text"
}
},
"history_records": {
"source": "cm_history_records",
"displayField": "id",
Expand Down
8 changes: 5 additions & 3 deletions app/plugins/Transmogrify/src/Command/TransmogrifyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,22 @@
use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException;
use Transmogrify\Lib\Enum\TransmogrifyEnum;
use Transmogrify\Lib\Traits\ActionCodeMapperTrait;
use Transmogrify\Lib\Traits\CacheTrait;
use Transmogrify\Lib\Traits\HookRunnersTrait;
use Transmogrify\Lib\Traits\ManageDefaultsTrait;
use Transmogrify\Lib\Traits\RowTransformationTrait;
use Transmogrify\Lib\Traits\TypeMapperTrait;
use Transmogrify\Lib\Util\CommandLinePrinter;
use Transmogrify\Lib\Util\DbInfoPrinter;
use Transmogrify\Lib\Util\RawSqlQueries;
use Transmogrify\Lib\Util\OrgIdentitiesHealth;
use Transmogrify\Lib\Util\GroupsHealth;
use Transmogrify\Lib\Util\OrgIdentitiesHealth;
use Transmogrify\Lib\Util\RawSqlQueries;

class TransmogrifyCommand extends BaseCommand {
use CacheTrait;
use TypeMapperTrait;
use ActionCodeMapperTrait;
use RowTransformationTrait;
use ManageDefaultsTrait;
use HookRunnersTrait;
Expand Down Expand Up @@ -288,7 +290,7 @@ public function execute(Arguments $args, ConsoleIo $io): int
if(!empty($this->tables[$t]['source'])) {
$src = $this->tables[$t]['source'];
if (!$this->tableExists($src)) {
$this->io->warning("Source table '$src' does not exist in source database, skipping table '$t'");
$this->cmdPrinter->warning("Source table '$src' does not exist in source database, skipping table '$t'");
continue;
}
}
Expand Down
240 changes: 240 additions & 0 deletions app/plugins/Transmogrify/src/Lib/Traits/ActionCodeMapperTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
<?php

/**
* COmanage Registry Transmogrify Command / Action Code Mapper Trait
*
* Portions licensed to the University Corporation for Advanced Internet
* Development, Inc. ("UCAID") under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership.
*
* UCAID licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @link https://www.internet2.edu/comanage COmanage Project
* @package registry
* @since COmanage Registry v5.2.0
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

declare(strict_types = 1);

namespace Transmogrify\Lib\Traits;

trait ActionCodeMapperTrait
{
/**
* v4 ActionEnum codes that map directly to v5 ActionEnum codes (same code).
*
* Keys are v4 right-hand codes, values are v5 right-hand codes.
*
* @var array<string, string>
*/
protected const ACTION_CODE_DIRECT_MAP = [
// Authenticators
'EAUT' => 'EAUT', // AuthenticatorEdited

// Comments
'CMNT' => 'CMNT', // CommentAdded

// Email
'EMLV' => 'EMLV', // EmailAddressVerified -> EmailVerified
'EMLS' => 'EMLS', // EmailAddressVerifyReqSent -> EmailVerifyCodeSent

// External Identity / Login env
'EOIE' => 'EOIE', // OrgIdEditedLoginEnv -> ExternalIdentityLoginUpdate

// Groups
'ACGR' => 'ACGR', // CoGroupAdded -> GroupAdded
'DCGR' => 'DCGR', // CoGroupDeleted -> GroupDeleted
'ECGR' => 'ECGR', // CoGroupEdited -> GroupEdited

// Group Members
'ACGM' => 'ACGM', // CoGroupMemberAdded -> GroupMemberAdded
'DCGM' => 'DCGM', // CoGroupMemberDeleted -> GroupMemberDeleted
'ECGM' => 'ECGM', // CoGroupMemberEdited -> GroupMemberEdited

// Identifiers / Matching
'AIDA' => 'AIDA', // IdentifierAutoAssigned
'UMAT' => 'UMAT', // MatchAttributesUpdated

// Names
'PNAM' => 'PNAM', // NamePrimary

// Notifications
'NOTA' => 'NOTA', // NotificationAcknowledged
'NOTX' => 'NOTX', // NotificationCanceled
'NOTD' => 'NOTD', // NotificationDelivered
'NOTR' => 'NOTR', // NotificationResolved

// Person pipeline
'ACPP' => 'ACPP', // CoPersonAddedPetition -> PersonAddedPetition
'ACPL' => 'ACPL', // CoPersonAddedPipeline -> PersonAddedPipeline
'MCPL' => 'MCPL', // CoPersonMatchedPipeline -> PersonMatchedPipeline

// Person status
'RCPS' => 'RCPS', // CoPersonStatusRecalculated -> PersonStatusRecalculated

// Petitions
'CPPC' => 'CPPC', // CoPetitionCreated -> PetitionCreated
'CPUP' => 'CPUP', // CoPetitionUpdated -> PetitionUpdated

// Reference ID
'OIDR' => 'OIDR', // ReferenceIdentifierObtained
];

/**
* v4 codes that map to v5 ActionEnum with a changed code.
*
* @var array<string, string>
*/
protected const ACTION_CODE_RENAMED_MAP = [
// CoPersonRoleRelinked (LCRM) -> PersonRoleRelinked (LCPR)
'LCRM' => 'LCPR',
];

/**
* v4 ActionEnum codes that moved to PetitionActionEnum in v5.
*
* Keys are v4 codes, values are v5 PetitionActionEnum codes.
*
* @var array<string, string>
*/
protected const ACTION_CODE_PETITION_MAP = [
// Invitations moved into PetitionActionEnum
'INVC' => 'IC', // InvitationConfirmed -> Accepted
'INVD' => 'PX', // InvitationDeclined -> Declined
'INVV' => 'IV', // InvitationViewed -> InvitationViewed
// 'INVE' => null, // InvitationExpired (no explicit equivalent)
// 'INVS' => null, // InvitationSent (no explicit equivalent)
];

/**
* Optional/opinionated mappings to collapse specific attribute events (names)
* into v5’s generic MVEA* events. Disabled by default for correctness.
*
* @var array<string, string>
*/
protected const ACTION_CODE_OPTIONAL_OPINIONATED_MAP = [
// Names -> generic Multi-Valued Extended Attribute events
'ANAM' => 'AMVE', // NameAdded -> MVEAAdded
'ENAM' => 'EMVE', // NameEdited -> MVEAEdited
'DNAM' => 'DMVE', // NameDeleted -> MVEADeleted
];

/**
* Map a v4 ActionEnum right-hand code to v5.
*
* Returns:
* - enum: 'ActionEnum' | 'PetitionActionEnum' | null
* - code: string|null
*
* When enum is null, there is no v5 equivalent; callers can log/skip.
*
* @param string $v4Code
* @param bool $enableOpinionated Enable optional generalized mappings (default=false)
* @return array{enum: string|null, code: string|null}
*/
protected function mapActionCode(string $v4Code, bool $enableOpinionated = false): array
{
$key = strtoupper(trim($v4Code));

if (isset(self::ACTION_CODE_DIRECT_MAP[$key])) {
return ['enum' => 'ActionEnum', 'code' => self::ACTION_CODE_DIRECT_MAP[$key]];
}

if (isset(self::ACTION_CODE_RENAMED_MAP[$key])) {
return ['enum' => 'ActionEnum', 'code' => self::ACTION_CODE_RENAMED_MAP[$key]];
}

if (isset(self::ACTION_CODE_PETITION_MAP[$key])) {
return ['enum' => 'PetitionActionEnum', 'code' => self::ACTION_CODE_PETITION_MAP[$key]];
}

if ($enableOpinionated && isset(self::ACTION_CODE_OPTIONAL_OPINIONATED_MAP[$key])) {
return ['enum' => 'ActionEnum', 'code' => self::ACTION_CODE_OPTIONAL_OPINIONATED_MAP[$key]];
}

return ['enum' => null, 'code' => null];
}

/**
* Convenience: map from a row array. Tries 'action' first, then 'action_code'.
*
* @param array $row
* @param bool $enableOpinionated
* @return array{enum: string|null, code: string|null}
*/
protected function mapActionFromRow(array $row, bool $enableOpinionated = false): array
{
$code = null;

if (isset($row['action']) && is_string($row['action'])) {
$code = $row['action'];
}

if ($code === null) {
return ['enum' => null, 'code' => null];
}

return $this->mapActionCode($code, $enableOpinionated);
}

/**
* Map a cm_co_notifications row’s action code to a v5 ActionEnum code.
* For notifications we only accept ActionEnum; PetitionActionEnum mappings return null.
*/
protected function mapNotificationAction(array $row): ?string
{
$m = $this->mapActionFromRow($row);

if ($m['enum'] === 'ActionEnum') {
return $m['code'];
}

// No ActionEnum equivalent (eg, invitation events that moved to PetitionActionEnum)
$rawCode = null;
if (isset($row['action']) && is_string($row['action'])) {
$rawCode = $row['action'];
}

if ($rawCode !== null && isset($this->cmdPrinter)) {
$unmapped = $this->listUnmappedActionCodes([$rawCode], false);
if (!empty($unmapped)) {
$this->cmdPrinter->warning(sprintf('Skipping notification with unmapped action code: %s', $unmapped[0]));
}
}

return null;
}

/**
* Report which v4 action codes won’t map under current settings.
*
* @param string[] $seenV4Codes
* @param bool $enableOpinionated
* @return string[]
*/
protected function listUnmappedActionCodes(array $seenV4Codes, bool $enableOpinionated = false): array
{
$unmapped = [];

foreach ($seenV4Codes as $c) {
$m = $this->mapActionCode((string)$c, $enableOpinionated);
if ($m['enum'] === null) {
$unmapped[] = strtoupper(trim((string)$c));
}
}

return array_values(array_unique($unmapped));
}
}

0 comments on commit 0d02d9d

Please sign in to comment.