diff --git a/resources/sql/20230427.semistructured.2.objectinstance.sql b/resources/sql/20230427.semistructured.2.objectinstance.sql index dec0fff..864c616 100644 --- a/resources/sql/20230427.semistructured.2.objectinstance.sql +++ b/resources/sql/20230427.semistructured.2.objectinstance.sql @@ -1,17 +1,18 @@ CREATE TABLE {$NAMESPACE}_semistructured.semistructured_objectinstance ( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, phid VARBINARY(64) NOT NULL, dateCreated INT UNSIGNED NOT NULL, dateModified INT UNSIGNED NOT NULL, classID INT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL COLLATE {$COLLATE_SORT}, + description LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}, rawData LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}, UNIQUE KEY `key_phid` (phid), KEY `classID` (classID) ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; CREATE TABLE {$NAMESPACE}_semistructured.semistructured_objectinstancetransaction LIKE {$NAMESPACE}_file.file_transaction; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index b3b2e34..efd1bf5 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,97 +1,99 @@ 2, 'class' => array( 'SemiStructuredBaseController' => 'applications/semistruct/controller/SemiStructuredBaseController.php', 'SemiStructuredDAO' => 'applications/semistruct/storage/SemiStructuredDAO.php', 'SemiStructuredDataApplication' => 'applications/semistruct/application/SemiStructuredDataApplication.php', 'SemiStructuredObjectInstance' => 'applications/semistruct/storage/SemiStructuredObjectInstance.php', 'SemiStructuredObjectInstanceClassTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceClassTransaction.php', 'SemiStructuredObjectInstanceController' => 'applications/semistruct/controller/instance/SemiStructuredObjectInstanceController.php', + 'SemiStructuredObjectInstanceDescriptionTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceDescriptionTransaction.php', 'SemiStructuredObjectInstanceEditController' => 'applications/semistruct/controller/instance/SemiStructuredObjectInstanceEditController.php', 'SemiStructuredObjectInstanceEditEngine' => 'applications/semistruct/editor/SemiStructuredObjectInstanceEditEngine.php', 'SemiStructuredObjectInstanceNameTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceNameTransaction.php', 'SemiStructuredObjectInstancePHIDType' => 'applications/semistruct/phid/SemiStructuredObjectInstancePHIDType.php', 'SemiStructuredObjectInstanceQuery' => 'applications/semistruct/query/SemiStructuredObjectInstanceQuery.php', 'SemiStructuredObjectInstanceRawDataTransaction' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceRawDataTransaction.php', 'SemiStructuredObjectInstanceSearchEngine' => 'applications/semistruct/query/SemiStructuredObjectInstanceSearchEngine.php', 'SemiStructuredObjectInstanceTransaction' => 'applications/semistruct/storage/SemiStructuredObjectInstanceTransaction.php', 'SemiStructuredObjectInstanceTransactionEditor' => 'applications/semistruct/editor/SemiStructuredObjectInstanceTransactionEditor.php', 'SemiStructuredObjectInstanceTransactionType' => 'applications/semistruct/xaction/instance/SemiStructuredObjectInstanceTransactionType.php', 'SemiStructuredObjectInstanceViewController' => 'applications/semistruct/controller/instance/SemiStructuredObjectInstanceViewController.php', 'SemiStructuredObjectNewInstanceController' => 'applications/semistruct/controller/class/SemiStructuredObjectNewInstanceController.php', 'SemiStructuredObjectType' => 'applications/semistruct/storage/SemiStructuredObjectType.php', 'SemiStructuredObjectTypeController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeController.php', 'SemiStructuredObjectTypeDescriptionTransaction' => 'applications/semistruct/xaction/class/SemiStructuredObjectTypeDescriptionTransaction.php', 'SemiStructuredObjectTypeEditController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeEditController.php', 'SemiStructuredObjectTypeEditEngine' => 'applications/semistruct/editor/SemiStructuredObjectTypeEditEngine.php', 'SemiStructuredObjectTypeFerretEngine' => 'applications/semistruct/engine/SemiStructuredObjectTypeFerretEngine.php', 'SemiStructuredObjectTypeListController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeListController.php', 'SemiStructuredObjectTypeNameTransaction' => 'applications/semistruct/xaction/class/SemiStructuredObjectTypeNameTransaction.php', 'SemiStructuredObjectTypePHIDType' => 'applications/semistruct/phid/SemiStructuredObjectTypePHIDType.php', 'SemiStructuredObjectTypeQuery' => 'applications/semistruct/query/SemiStructuredObjectTypeQuery.php', 'SemiStructuredObjectTypeSearchEngine' => 'applications/semistruct/query/SemiStructuredObjectTypeSearchEngine.php', 'SemiStructuredObjectTypeTransaction' => 'applications/semistruct/storage/SemiStructuredObjectTypeTransaction.php', 'SemiStructuredObjectTypeTransactionEditor' => 'applications/semistruct/editor/SemiStructuredObjectTypeTransactionEditor.php', 'SemiStructuredObjectTypeTransactionType' => 'applications/semistruct/xaction/class/SemiStructuredObjectTypeTransactionType.php', 'SemiStructuredObjectTypeViewController' => 'applications/semistruct/controller/class/SemiStructuredObjectTypeViewController.php', 'SemiStructuredPatchList' => 'applications/semistruct/storage/SemiStructuredPatchList.php', ), 'function' => array(), 'xmap' => array( 'SemiStructuredBaseController' => 'PhabricatorController', 'SemiStructuredDAO' => 'PhabricatorLiskDAO', 'SemiStructuredDataApplication' => 'PhabricatorApplication', 'SemiStructuredObjectInstance' => array( 'SemiStructuredDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', ), 'SemiStructuredObjectInstanceClassTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstanceController' => 'SemiStructuredBaseController', + 'SemiStructuredObjectInstanceDescriptionTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstanceEditController' => 'SemiStructuredBaseController', 'SemiStructuredObjectInstanceEditEngine' => 'PhabricatorEditEngine', 'SemiStructuredObjectInstanceNameTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstancePHIDType' => 'PhabricatorPHIDType', 'SemiStructuredObjectInstanceQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'SemiStructuredObjectInstanceRawDataTransaction' => 'SemiStructuredObjectInstanceTransactionType', 'SemiStructuredObjectInstanceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'SemiStructuredObjectInstanceTransaction' => 'PhabricatorModularTransaction', 'SemiStructuredObjectInstanceTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'SemiStructuredObjectInstanceTransactionType' => 'PhabricatorModularTransactionType', 'SemiStructuredObjectInstanceViewController' => 'SemiStructuredObjectInstanceController', 'SemiStructuredObjectNewInstanceController' => 'SemiStructuredObjectTypeController', 'SemiStructuredObjectType' => array( 'SemiStructuredDAO', 'PhabricatorPolicyInterface', 'PhabricatorApplicationTransactionInterface', 'PhabricatorFlaggableInterface', 'PhabricatorDestructibleInterface', 'PhabricatorFerretInterface', ), 'SemiStructuredObjectTypeController' => 'SemiStructuredBaseController', 'SemiStructuredObjectTypeDescriptionTransaction' => 'SemiStructuredObjectTypeTransactionType', 'SemiStructuredObjectTypeEditController' => 'SemiStructuredBaseController', 'SemiStructuredObjectTypeEditEngine' => 'PhabricatorEditEngine', 'SemiStructuredObjectTypeFerretEngine' => 'PhabricatorFerretEngine', 'SemiStructuredObjectTypeListController' => 'SemiStructuredBaseController', 'SemiStructuredObjectTypeNameTransaction' => 'SemiStructuredObjectTypeTransactionType', 'SemiStructuredObjectTypePHIDType' => 'PhabricatorPHIDType', 'SemiStructuredObjectTypeQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'SemiStructuredObjectTypeSearchEngine' => 'PhabricatorApplicationSearchEngine', 'SemiStructuredObjectTypeTransaction' => 'PhabricatorModularTransaction', 'SemiStructuredObjectTypeTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'SemiStructuredObjectTypeTransactionType' => 'PhabricatorModularTransactionType', 'SemiStructuredObjectTypeViewController' => 'SemiStructuredObjectTypeController', 'SemiStructuredPatchList' => 'PhabricatorSQLPatchList', ), )); diff --git a/src/controller/instance/SemiStructuredObjectInstanceViewController.php b/src/controller/instance/SemiStructuredObjectInstanceViewController.php index ff1cdfc..e5a2f91 100644 --- a/src/controller/instance/SemiStructuredObjectInstanceViewController.php +++ b/src/controller/instance/SemiStructuredObjectInstanceViewController.php @@ -1,103 +1,109 @@ getViewer(); $id = $request->getURIData('id'); $object = id(new SemiStructuredObjectInstanceQuery()) ->setViewer($viewer) ->withIDs(array($id)) ->executeOne(); if (!$object) { return new Aphront404Response(); } $this->setObject($object); $object_type = $object->getClass(); $crumbs = $this->buildApplicationCrumbs(); $title = pht( "%s: %d %s", $object_type->getName(), $object->getID(), $object->getName()); $header = $this->buildHeaderView(); $curtain = $this->buildCurtain(); $details = $this->buildDetailsView(); // $timeline = $this->buildTransactionTimeline( // $object_type, // new PhabricatorBadgesTransactionQuery()); /// TODO $timeline = null; $comment_view = id(new SemiStructuredObjectInstanceEditEngine()) ->setViewer($viewer) ->buildEditEngineCommentView($object); $view = id(new PHUITwoColumnView()) ->setHeader($header) ->setCurtain($curtain) ->setMainColumn(array( $timeline, $comment_view, )) ->addPropertySection(pht('Details'), $details); $navigation = $this->buildSideNavView('view'); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->setPageObjectPHIDs(array($object->getPHID())) ->setNavigation($navigation) ->appendChild($view); } private function buildDetailsView() { $viewer = $this->getViewer(); $object = $this->getObject(); $view = id(new PHUIPropertyListView()) ->setUser($viewer); $content = $object->getRawData(); $content = id(new PhutilJSON()) ->encodeFormatted(json_decode($content)); + $description = $object->getDescription(); + if (strlen($description)) { + $view->addTextContent( + new PHUIRemarkupView($viewer, $description)); + } + $view->addProperty( - pht('Unstructured content'), + pht('Structured content'), phutil_tag('pre', array(), $content)); return $view; } private function buildCurtain() { $viewer = $this->getViewer(); $object = $this->getObject(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $object, PhabricatorPolicyCapability::CAN_EDIT); $id = $object->getID(); $edit_uri = $this->getApplicationURI("/editinstance/{$id}/"); $curtain = $this->newCurtainView($object); $curtain->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Instance')) ->setIcon('fa-pencil') ->setDisabled(!$can_edit) ->setHref($edit_uri)); return $curtain; } } diff --git a/src/editor/SemiStructuredObjectInstanceEditEngine.php b/src/editor/SemiStructuredObjectInstanceEditEngine.php index ffd2a13..84d62a5 100644 --- a/src/editor/SemiStructuredObjectInstanceEditEngine.php +++ b/src/editor/SemiStructuredObjectInstanceEditEngine.php @@ -1,124 +1,133 @@ objectType = $object_type; return $this; } protected function newEditableObject() { $viewer = $this->getViewer(); return SemiStructuredObjectInstance::initializeNewObjectInstance( $viewer, $this->objectType); } protected function newObjectQuery() { return new SemiStructuredObjectInstanceQuery(); } protected function getObjectCreateTitleText($object) { return pht('Create Object Instance'); } protected function getObjectCreateButtonText($object) { return pht('Create Object Instance'); } protected function getObjectCreateCancelURI($object) { // TODO if objjec has link to class - go to that class return '/semistructured/'; } protected function getEditorURI() { return $this->getApplication()->getApplicationURI('editobject/'); } protected function getObjectEditTitleText($object) { return pht('Edit Object: %d', $object->getID()); } protected function getObjectEditShortText($object) { return pht('Edit Object'); } protected function getObjectCreateShortText() { return pht('Create Object Instance'); } protected function getObjectName() { return pht('Object Instance'); } protected function getObjectViewURI($object) { return $object->getURI(); } protected function buildCustomEditFields($object) { $fields = array( id(id(new PhabricatorStaticEditField()) ->setKey('objecttype') ->setLabel(pht("Object Type")) ->setValue($object->getClass()->getName())), id(new PhabricatorIntEditField()) ->setKey('classID') ->setLabel(pht('Object Type')) ->setDescription(pht('Type of the object.')) ->setConduitDescription(pht('Set type of object.')) ->setIsHidden(true) ->setTransactionType( SemiStructuredObjectInstanceClassTransaction::TRANSACTIONTYPE) ->setIsRequired(true) ->setValue($object->getClassID()), id(new PhabricatorTextEditField()) ->setKey('name') ->setLabel(pht('Name')) ->setDescription(pht('Name of the object.')) ->setConduitDescription(pht('Rename the object.')) ->setConduitTypeDescription(pht('New object name.')) ->setTransactionType( SemiStructuredObjectInstanceNameTransaction::TRANSACTIONTYPE) ->setValue($object->getName()), + id(new PhabricatorRemarkupEditField()) + ->setKey('description') + ->setLabel(pht('Free text')) + ->setDescription(pht('Object instance free text (Remarkup).')) + ->setConduitTypeDescription(pht('New object free type.')) + ->setTransactionType( + SemiStructuredObjectInstanceDescriptionTransaction::TRANSACTIONTYPE) + ->setValue($object->getDescription()), + id(new PhabricatorTextAreaEditField()) ->setKey('rawdata') - ->setLabel(pht('Unstructured content')) - ->setDescription(pht('Object unstructured data (JSON).')) + ->setLabel(pht('Structured content')) + ->setDescription(pht('Object structured data (JSON).')) ->setConduitTypeDescription(pht('Object body (JSON).')) ->setMonospaced(true) ->setIsRequired(true) ->setTransactionType( SemiStructuredObjectInstanceRawDataTransaction::TRANSACTIONTYPE) ->setValue($object->getRawData()), ); return $fields; } } diff --git a/src/storage/SemiStructuredObjectInstance.php b/src/storage/SemiStructuredObjectInstance.php index eaf7a56..04d1b85 100644 --- a/src/storage/SemiStructuredObjectInstance.php +++ b/src/storage/SemiStructuredObjectInstance.php @@ -1,142 +1,146 @@ setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) // TODO take policies from class? // ->setEditPolicy($actor->getPHID()) ->setStatus(self::STATUS_ACTIVE); if ($object_class !== null) { $object ->setClassID($object_class->getID()) ->attachClass($object_class); } return $object; } public function attachClass( SemiStructuredObjectType $object_class = null) { $this->class = $object_class; return $this; } public function getClass() { return $this->assertAttached($this->class); } public function getURI() { return urisprintf('/semistruct/instance/%d/', $this->getID()); } public function getIcon() { // TODO return 'fa-google'; } public static function getStatusNameMap() { return array( // TODO remove this self::STATUS_ACTIVE => pht('Active'), self::STATUS_ARCHIVED => pht('Archived'), ); } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, + self::CONFIG_SERIALIZATION => array( + 'rawData' => self::SERIALIZATION_JSON, + ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'sort255', - 'rawData' => 'text', // TODO maybe it should be marked as "json"? + 'description' => 'text', + 'status' => 'text32', ), self::CONFIG_KEY_SCHEMA => array( 'classID' => array( 'columns' => array('classID'), ), ), ) + parent::getConfiguration(); } public function getPHIDType() { return SemiStructuredObjectInstancePHIDType::TYPECONST; } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new SemiStructuredObjectInstanceTransactionEditor(); } public function getApplicationTransactionTemplate() { return new SemiStructuredObjectInstanceTransaction(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { return; switch ($capability) { // TODO case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return true; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $this->delete(); $this->saveTransaction(); } } diff --git a/src/xaction/instance/SemiStructuredObjectInstanceDescriptionTransaction.php b/src/xaction/instance/SemiStructuredObjectInstanceDescriptionTransaction.php new file mode 100644 index 0000000..abe7bdc --- /dev/null +++ b/src/xaction/instance/SemiStructuredObjectInstanceDescriptionTransaction.php @@ -0,0 +1,57 @@ +getDescription(); + } + + public function applyInternalEffects($object, $value) { + $object->setDescription($value); + } + + public function getTitle() { + return pht( + '%s updated the object type description.', + $this->renderAuthor()); + } + + public function getTitleForFeed() { + return pht( + '%s updated the description for object type %s.', + $this->renderAuthor(), + $this->renderObject()); + } + + public function hasChangeDetailView() { + return true; + } + + public function getMailDiffSectionHeader() { + return pht('CHANGES TO OBJECT INSTANCE DESCRIPTION'); + } + + public function newChangeDetailView() { + $viewer = $this->getViewer(); + + return id(new PhabricatorApplicationTransactionTextDiffDetailView()) + ->setViewer($viewer) + ->setOldText($this->getOldValue()) + ->setNewText($this->getNewValue()); + } + + public function newRemarkupChanges() { + $changes = array(); + + $changes[] = $this->newRemarkupChange() + ->setOldValue($this->getOldValue()) + ->setNewValue($this->getNewValue()); + + return $changes; + } + + +} diff --git a/src/xaction/instance/SemiStructuredObjectInstanceRawDataTransaction.php b/src/xaction/instance/SemiStructuredObjectInstanceRawDataTransaction.php index 9816db4..0d4c9f0 100644 --- a/src/xaction/instance/SemiStructuredObjectInstanceRawDataTransaction.php +++ b/src/xaction/instance/SemiStructuredObjectInstanceRawDataTransaction.php @@ -1,82 +1,83 @@ getRawData(); } public function applyInternalEffects($object, $value) { + // TODO this might need changing ? $object->setRawData($value); } public function getTitle() { return pht( '%s updated the content of the object.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s updated the content for object %s.', $this->renderAuthor(), $this->renderObject()); } public function getMailDiffSectionHeader() { return pht('CHANGES TO CONTENT OF OBJECT INSTANCE'); } public function hasChangeDetailView() { return true; } public function newChangeDetailView() { $viewer = $this->getViewer(); $old = $this->getOldValue(); $new = $this->getNewValue(); $json = new PhutilJSON(); // $old_json = $json->encodeFormatted($old); // $new_json = $json->encodeFormatted($new); return id(new PhabricatorApplicationTransactionTextDiffDetailView()) ->setViewer($viewer) ->setOldText($old) ->setNewText($new); } public function validateTransactions($object, array $xactions) { $errors = array(); if (!$xactions) { return $errors; } $json_parser = new PhutilJSONParser(); if ($this->isEmptyTextTransaction($object->getRawData(), $xactions)) { $errors[] = $this->newRequiredError( pht('Object must have content.')); } foreach ($xactions as $xaction) { $new_value = $xaction->getNewValue(); try { phutil_json_decode($new_value); } catch (PhutilJSONParserException $ex) { $errors[] = $this->newInvalidError( pht('Object body must be valid json!')); } } return $errors; } }