From 74ba50859c3f824cd433147c6271926b74489aca Mon Sep 17 00:00:00 2001 From: Benn Oshrin Date: Thu, 26 Aug 2021 15:37:24 -0400 Subject: [PATCH] Add Suspended Confidence mode (CO-2114) and duplication of Rules and Attributes (CO-2191) --- app/src/Controller/AttributesController.php | 1 + app/src/Controller/RulesController.php | 1 + app/src/Controller/StandardController.php | 80 +++++++++++++++++++++ app/src/Lib/Enum/ConfidenceModeEnum.php | 3 +- app/src/Lib/Traits/AssociationTrait.php | 31 +++++++- app/src/Lib/Traits/PrimaryLinkTrait.php | 2 +- app/src/Locale/en_US/default.po | 18 +++++ app/src/Model/Table/RulesTable.php | 14 ++-- app/src/Template/Element/javascript.ctp | 7 ++ app/src/Template/Standard/index.ctp | 8 +++ 10 files changed, 150 insertions(+), 15 deletions(-) diff --git a/app/src/Controller/AttributesController.php b/app/src/Controller/AttributesController.php index fda0d1cf6..4885f641f 100644 --- a/app/src/Controller/AttributesController.php +++ b/app/src/Controller/AttributesController.php @@ -55,6 +55,7 @@ public function isAuthorized(Array $user) { $p = [ 'add' => $platformAdmin || $mgAdmin, 'delete' => $platformAdmin || $mgAdmin, + 'duplicate' => $platformAdmin || $mgAdmin, 'edit' => $platformAdmin || $mgAdmin, 'index' => $platformAdmin || $mgAdmin, 'view' => false diff --git a/app/src/Controller/RulesController.php b/app/src/Controller/RulesController.php index 992be6f86..cbcf7ee5e 100644 --- a/app/src/Controller/RulesController.php +++ b/app/src/Controller/RulesController.php @@ -56,6 +56,7 @@ public function isAuthorized(Array $user) { $p = [ 'add' => $platformAdmin || $mgAdmin, 'delete' => $platformAdmin || $mgAdmin, + 'duplicate' => $platformAdmin || $mgAdmin, 'edit' => $platformAdmin || $mgAdmin, 'index' => $platformAdmin || $mgAdmin, 'view' => false diff --git a/app/src/Controller/StandardController.php b/app/src/Controller/StandardController.php index 7448031dd..bf4dbea11 100644 --- a/app/src/Controller/StandardController.php +++ b/app/src/Controller/StandardController.php @@ -123,6 +123,86 @@ public function delete($id) { return $this->generateRedirect(); } + /** + * Handle a duplicate action for a Standard object. + * + * @since COmanage Match v1.0.0 + * @param Integer $id Object ID + */ + + public function duplicate($id) { + // $this->name = Models (ie: from ModelsTable) + $modelsName = $this->name; + $associatedModels = []; + + $query = $this->$modelsName->findById($id); + + // AssociationTrait + if(method_exists($this->$modelsName, "getDuplicateContains")) { + $query = $query->contain($this->$modelsName->getDuplicateContains()); + $associatedModels = $this->$modelsName->getDuplicateContains(); + } + + // Make sure the requested object exists + try { + $obj = $query->firstOrFail(); + + // Create a new entity. We disable validation in case the validation rules + // changed after the original object was created. + $newObj = $this->$modelsName->newEntity($obj->toArray(), ['validate' => false]); + + // The object ID is cleared by newEntity, but not the timestamps + unset($newObj->created); + unset($newObj->modified); + + foreach($associatedModels as $am) { + $at = \Cake\Utility\Inflector::tableize($am); + + if(!empty($newObj->$at)) { + foreach($newObj->$at as $ao) { + unset($ao->created); + unset($ao->modified); + } + } + } + + // Edit the name of the parent object (but we don't rename related models) + if(!empty($newObj->name)) { + $newObj->name = __('match.fd.copy_of', [$newObj->name]); + } + + if($this->$modelsName->save($newObj)) { + // Use the display field to generate the flash message + + $field = $this->$modelsName->getDisplayField(); + + if(!empty($newObj->$field)) { + $this->Flash->success(__('match.rs.duplicated.a', [$newObj->$field])); + } else { + $this->Flash->success(__('match.rs.duplicated')); + } + + // Redirect to edit view + $redirect = [ + 'action' => 'edit', + $newObj->id + ]; + + return $this->redirect($redirect); + } else { + // It's hard to get a specific failure reason to render... + $this->Flash->error(__('match.er.duplicate')); + } + } + catch(\Exception $e) { + // findById throws Cake\Datasource\Exception\RecordNotFoundException + + $this->Flash->error($e->getMessage()); + } + + return $this->generateRedirect(); + } + /** * Handle an edit action for a Standard object. * diff --git a/app/src/Lib/Enum/ConfidenceModeEnum.php b/app/src/Lib/Enum/ConfidenceModeEnum.php index caec3e6a3..9b9a6481d 100644 --- a/app/src/Lib/Enum/ConfidenceModeEnum.php +++ b/app/src/Lib/Enum/ConfidenceModeEnum.php @@ -19,7 +19,7 @@ * See the License for the specific language governing permissions and * limitations under the License. * - * @link http://www.internet2.edu/comanage COmanage Project + * @link https://www.internet2.edu/comanage COmanage Project * @package match * @since COmanage Match v1.0.0 * @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0) @@ -32,4 +32,5 @@ class ConfidenceModeEnum extends StandardEnum { const Canonical = 'C'; const Potential = 'P'; + const Suspended = 'S'; } \ No newline at end of file diff --git a/app/src/Lib/Traits/AssociationTrait.php b/app/src/Lib/Traits/AssociationTrait.php index c53729aca..450f24078 100644 --- a/app/src/Lib/Traits/AssociationTrait.php +++ b/app/src/Lib/Traits/AssociationTrait.php @@ -30,14 +30,28 @@ namespace App\Lib\Traits; trait AssociationTrait { + // Array of associated models to copy during a duplicate + private $duplicateContains = false; + // Array of associated models to pull during an edit - private $editContains = null; + private $editContains = false; // Array of associated models to save during a patch - private $patchAssociated = null; + private $patchAssociated = false; // Array of associated models to pull during a view - private $viewContains = null; + private $viewContains = false; + + /** + * Obtain the set of associated models to copy during a duplicate. + * + * @since COmanage Match v1.0.0 + * @return array Array of associated models + */ + + public function getDuplicateContains() { + return $this->duplicateContains; + } /** * Obtain the set of associated models to pull during an edit. @@ -72,6 +86,17 @@ public function getViewContains() { return $this->viewContains; } + /** + * Set the associated models to copy during a duplicate. + * + * @since COmanage Match v1.0.0 + * @param array $c Array of associated models + */ + + public function setDuplicateContains(array $c) { + $this->duplicateContains = $c; + } + /** * Set the associated models to pull during an edit. * diff --git a/app/src/Lib/Traits/PrimaryLinkTrait.php b/app/src/Lib/Traits/PrimaryLinkTrait.php index 83f6f7400..48e94ce7c 100644 --- a/app/src/Lib/Traits/PrimaryLinkTrait.php +++ b/app/src/Lib/Traits/PrimaryLinkTrait.php @@ -43,7 +43,7 @@ trait PrimaryLinkTrait { private $unkeyedActions = ['add', 'index']; // Actions where the primary link can be obtained by looking up the record ID - private $lookupActions = ['delete', 'edit', 'view']; + private $lookupActions = ['delete', 'duplicate', 'edit', 'view']; /** * Whether the primary link is permitted to be empty. diff --git a/app/src/Locale/en_US/default.po b/app/src/Locale/en_US/default.po index f773338b1..01a827226 100644 --- a/app/src/Locale/en_US/default.po +++ b/app/src/Locale/en_US/default.po @@ -149,6 +149,9 @@ msgstr "Canonical" msgid "match.en.ConfidenceModeEnum.P" msgstr "Potential" +msgid "match.en.ConfidenceModeEnum.S" +msgstr "Suspended" + msgid "match.en.PermissionEnum.A" msgstr "Platform Administrator" @@ -222,6 +225,9 @@ msgstr "Possibly failed to update database schema" msgid "match.er.delete" msgstr "Delete Failed" +msgid "match.er.duplicate" +msgstr "Duplicate Failed" + msgid "match.er.file" msgstr "Cannot read file {0}" @@ -351,6 +357,9 @@ msgstr "Case Sensitive" msgid "match.fd.confidence_mode" msgstr "Confidence Mode" +msgid "match.fd.copy_of" +msgstr "Copy of {0}" + msgid "match.fd.description" msgstr "Description" @@ -504,6 +513,9 @@ msgstr "Are you sure you wish to delete this record ({0})?" msgid "match.op.display" msgstr "Display" +msgid "match.op.duplicate" +msgstr "Duplicate" + msgid "match.op.edit" msgstr "Edit" @@ -583,6 +595,12 @@ msgstr "Deleted" msgid "match.rs.deleted.a" msgstr "{0} Deleted" +msgid "match.rs.duplicated" +msgstr "Duplicated" + +msgid "match.rs.duplicated.a" +msgstr "{0} Duplicated" + msgid "match.rs.pending" msgstr "{0,plural,=1{# Pending Match} other{# Pending Matches}}" diff --git a/app/src/Model/Table/RulesTable.php b/app/src/Model/Table/RulesTable.php index e2f37a75c..618044f5b 100644 --- a/app/src/Model/Table/RulesTable.php +++ b/app/src/Model/Table/RulesTable.php @@ -57,20 +57,13 @@ public function initialize(array $config) { $this->setDisplayField('name'); - $this->setEditContains(['RuleAttributes']); - // During a save, also save RuleAttributes - $this->setPatchAssociated(['RuleAttributes']); + // AssociationTrait + $this->setDuplicateContains(['RuleAttributes']); $this->setPrimaryLink('matchgrid_id'); $this->setRequiresMatchgrid(true); $this->setAutoViewVars([ - 'attributes' => [ - 'type' => 'auxiliary', - 'model' => 'Attributes', - 'find' => 'filterPrimaryLink', - 'order' => ['name' => 'ASC'] - ], 'confidenceModes' => [ 'type' => 'enum', 'class' => 'ConfidenceModeEnum' @@ -113,7 +106,8 @@ public function validationDefault(Validator $validator) { 'content', [ 'rule' => [ 'inList', [ ConfidenceModeEnum::Canonical, - ConfidenceModeEnum::Potential + ConfidenceModeEnum::Potential, + ConfidenceModeEnum::Suspended ] ] ] ); $validator->notEmpty('confidence_mode'); diff --git a/app/src/Template/Element/javascript.ctp b/app/src/Template/Element/javascript.ctp index fd5a29b6a..94e64e9ef 100644 --- a/app/src/Template/Element/javascript.ctp +++ b/app/src/Template/Element/javascript.ctp @@ -153,6 +153,13 @@ text: true }); + $(".copybutton").button({ + icons: { + primary: 'ui-icon-copy' + }, + text: true + }); + $(".deletebutton").button({ icons: { primary: 'ui-icon-circle-close' diff --git a/app/src/Template/Standard/index.ctp b/app/src/Template/Standard/index.ctp index 3d896640d..64e5f20a7 100644 --- a/app/src/Template/Standard/index.ctp +++ b/app/src/Template/Standard/index.ctp @@ -252,6 +252,14 @@ function _column_key($modelsName, $c, $tz=null) { ); } + if(isset($vv_permissions['duplicate']) && $vv_permissions['duplicate']) { + print $this->Html->link( + __($product.'.op.duplicate'), + array_merge(['action' => 'duplicate'], $linkArgs), + ['class' => 'copybutton'] + ); + } + if($vv_permissions['delete']) { // XXX this is throwing CSRF error even though delete button on edit-record page is working? // probably because this is using Form helper, but we're outside of a form?