@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add a book controller and various amenities to Diviner's live view

Summary: Ref T988. Mostly backend changes, with a very rough frontend on top of them. See Conpherence discussion.

Test Plan: {F45010}

Reviewers: btrahan, chad

Reviewed By: chad

CC: aran

Maniphest Tasks: T988

Differential Revision: https://secure.phabricator.com/D6113

+293 -38
+17
resources/sql/patches/20130602.morediviner.sql
··· 1 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livebook 2 + ADD configurationData LONGTEXT COLLATE utf8_bin NOT NULL; 3 + 4 + UPDATE {$NAMESPACE}_diviner.diviner_livebook 5 + SET configurationData = '{}' WHERE configurationData = ''; 6 + 7 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol 8 + ADD title VARCHAR(255); 9 + 10 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol 11 + ADD groupName VARCHAR(255); 12 + 13 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol 14 + ADD summary LONGTEXT COLLATE utf8_bin; 15 + 16 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol 17 + ADD isDocumentable BOOL NOT NULL;
+2
src/__phutil_library_map__.php
··· 514 514 'DivinerAtomSearchEngine' => 'applications/diviner/query/DivinerAtomSearchEngine.php', 515 515 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 516 516 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 517 + 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', 517 518 'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php', 518 519 'DivinerController' => 'applications/diviner/controller/DivinerController.php', 519 520 'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php', ··· 2342 2343 'DivinerAtomQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2343 2344 'DivinerAtomSearchEngine' => 'PhabricatorApplicationSearchEngine', 2344 2345 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 2346 + 'DivinerBookController' => 'DivinerController', 2345 2347 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 2346 2348 'DivinerController' => 'PhabricatorController', 2347 2349 'DivinerDAO' => 'PhabricatorLiskDAO',
+1
src/applications/diviner/application/PhabricatorApplicationDiviner.php
··· 25 25 'query/((?<key>[^/]+)/)?' => 'DivinerAtomListController', 26 26 ), 27 27 '/docs/(?P<keyword>[^/]+)/' => 'DivinerJumpController', 28 + '/book/(?P<book>[^/]+)/' => 'DivinerBookController', 28 29 '/book/'. 29 30 '(?P<book>[^/]+)/'. 30 31 '(?P<type>[^/]+)/'.
+1 -18
src/applications/diviner/controller/DivinerAtomListController.php
··· 24 24 } 25 25 26 26 public function renderResultsList(array $symbols) { 27 - assert_instances_of($symbols, 'DivinerLiveSymbol'); 28 - 29 - $request = $this->getRequest(); 30 - $user = $request->getUser(); 31 - 32 - $list = id(new PhabricatorObjectItemListView()) 33 - ->setUser($user); 34 - 35 - foreach ($symbols as $symbol) { 36 - $item = id(new PhabricatorObjectItemView()) 37 - ->setHeader($symbol->getName()) 38 - ->setHref($symbol->getURI()) 39 - ->addIcon('none', $symbol->getType()); 40 - 41 - $list->addItem($item); 42 - } 43 - 44 - return $list; 27 + return $this->renderAtomList($symbols); 45 28 } 46 29 47 30 }
+112
src/applications/diviner/controller/DivinerBookController.php
··· 1 + <?php 2 + 3 + final class DivinerBookController extends DivinerController { 4 + 5 + private $bookName; 6 + 7 + public function shouldAllowPublic() { 8 + return true; 9 + } 10 + 11 + public function willProcessRequest(array $data) { 12 + $this->bookName = $data['book']; 13 + } 14 + 15 + public function processRequest() { 16 + $request = $this->getRequest(); 17 + $viewer = $request->getUser(); 18 + 19 + $book = id(new DivinerBookQuery()) 20 + ->setViewer($viewer) 21 + ->withNames(array($this->bookName)) 22 + ->executeOne(); 23 + 24 + if (!$book) { 25 + return new Aphront404Response(); 26 + } 27 + 28 + $crumbs = $this->buildApplicationCrumbs(); 29 + 30 + $crumbs->addCrumb( 31 + id(new PhabricatorCrumbView()) 32 + ->setName($book->getTitle()) 33 + ->setHref('/book/'.$book->getName().'/')); 34 + 35 + $header = id(new PhabricatorHeaderView())->setHeader($book->getTitle()); 36 + $properties = $this->buildPropertyList($book); 37 + 38 + $atoms = id(new DivinerAtomQuery()) 39 + ->setViewer($viewer) 40 + ->withBookPHIDs(array($book->getPHID())) 41 + ->execute(); 42 + $atoms = msort($atoms, 'getSortKey'); 43 + 44 + $group_spec = $book->getConfig('groups'); 45 + if (!is_array($group_spec)) { 46 + $group_spec = array(); 47 + } 48 + 49 + $groups = mgroup($atoms, 'getGroupName'); 50 + $groups = array_select_keys($groups, array_keys($group_spec)) + $groups; 51 + if (isset($groups[''])) { 52 + $no_group = $groups['']; 53 + unset($groups['']); 54 + $groups[''] = $no_group; 55 + } 56 + 57 + $out = array(); 58 + foreach ($groups as $group => $atoms) { 59 + $group_info = idx($group_spec, $group); 60 + if (!is_array($group_info)) { 61 + $group_info = array(); 62 + } 63 + 64 + $group_name = idx($group_info, 'name'); 65 + if (!strlen($group_name)) { 66 + if (strlen($group)) { 67 + $group_name = $group; 68 + } else { 69 + $group_name = pht('Free Radicals'); 70 + } 71 + } 72 + 73 + $out[] = id(new PhabricatorHeaderView()) 74 + ->setHeader($group_name); 75 + $out[] = $this->renderAtomList($atoms); 76 + } 77 + 78 + return $this->buildApplicationPage( 79 + array( 80 + $crumbs, 81 + $header, 82 + $properties, 83 + $out, 84 + ), 85 + array( 86 + 'title' => $book->getTitle(), 87 + 'dust' => true, 88 + 'device' => true, 89 + )); 90 + } 91 + 92 + private function buildPropertyList(DivinerLiveBook $book) { 93 + $user = $this->getRequest()->getUser(); 94 + $view = id(new PhabricatorPropertyListView()) 95 + ->setUser($user); 96 + 97 + $policies = PhabricatorPolicyQuery::renderPolicyDescriptions( 98 + $user, 99 + $book); 100 + 101 + $view->addProperty( 102 + pht('Visible To'), 103 + $policies[PhabricatorPolicyCapability::CAN_VIEW]); 104 + 105 + $view->addProperty( 106 + pht('Updated'), 107 + phabricator_datetime($book->getDateModified(), $user)); 108 + 109 + return $view; 110 + } 111 + 112 + }
+23
src/applications/diviner/controller/DivinerController.php
··· 21 21 return $menu; 22 22 } 23 23 24 + protected function renderAtomList(array $symbols) { 25 + assert_instances_of($symbols, 'DivinerLiveSymbol'); 26 + 27 + $request = $this->getRequest(); 28 + $user = $request->getUser(); 29 + 30 + $list = id(new PhabricatorObjectItemListView()) 31 + ->setUser($user); 32 + 33 + foreach ($symbols as $symbol) { 34 + $item = id(new PhabricatorObjectItemView()) 35 + ->setHeader($symbol->getTitle()) 36 + ->setHref($symbol->getURI()) 37 + ->addIcon('none', $symbol->getType()); 38 + 39 + $item->addAttribute(phutil_safe_html($symbol->getSummary())); 40 + 41 + $list->addItem($item); 42 + } 43 + 44 + return $list; 45 + } 46 + 24 47 }
+29 -11
src/applications/diviner/publisher/DivinerLivePublisher.php
··· 18 18 ->save(); 19 19 } 20 20 21 + $book->setConfigurationData($this->getConfigurationData())->save(); 22 + 21 23 $this->book = $book; 22 24 } 23 25 return $this->book; 24 26 } 25 27 26 28 private function loadSymbolForAtom(DivinerAtom $atom) { 27 - $symbol = id(new DivinerLiveSymbol())->loadOneWhere( 28 - 'bookPHID = %s AND type = %s AND name = %s AND context = %ns 29 - AND atomIndex = %d', 30 - $this->loadBook()->getPHID(), 31 - $atom->getType(), 32 - $atom->getName(), 33 - $atom->getContext(), 34 - $this->getAtomSimilarIndex($atom)); 29 + $symbol = id(new DivinerAtomQuery()) 30 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 31 + ->withBookPHIDs(array($this->loadBook()->getPHID())) 32 + ->withTypes(array($atom->getType())) 33 + ->withNames(array($atom->getName())) 34 + ->withContexts(array($atom->getContext())) 35 + ->withIndexes(array($this->getAtomSimilarIndex($atom))) 36 + ->withIncludeUndocumentable(true) 37 + ->executeOne(); 35 38 36 39 if ($symbol) { 37 40 return $symbol; ··· 97 100 protected function createDocumentsByHash(array $hashes) { 98 101 foreach ($hashes as $hash) { 99 102 $atom = $this->getAtomFromGraphHash($hash); 103 + $ref = $atom->getRef(); 100 104 101 105 $symbol = $this->loadSymbolForAtom($atom); 102 - $symbol->setGraphHash($hash)->save(); 106 + 107 + $is_documentable = $this->shouldGenerateDocumentForAtom($atom); 108 + 109 + $symbol 110 + ->setGraphHash($hash) 111 + ->setIsDocumentable((int)$is_documentable) 112 + ->setTitle($ref->getTitle()) 113 + ->setGroupName($ref->getGroup()); 103 114 104 - if ($this->shouldGenerateDocumentForAtom($atom)) { 105 - $content = $this->getRenderer()->renderAtom($atom); 115 + if ($is_documentable) { 116 + $renderer = $this->getRenderer(); 117 + $content = $renderer->renderAtom($atom); 106 118 107 119 $storage = $this->loadAtomStorageForSymbol($symbol) 108 120 ->setAtomData($atom->toDictionary()) 109 121 ->setContent((string)phutil_safe_html($content)) 110 122 ->save(); 123 + 124 + $summary = $renderer->renderAtomSummary($atom); 125 + $summary = (string)phutil_safe_html($summary); 126 + $symbol->setSummary($summary); 111 127 } 128 + 129 + $symbol->save(); 112 130 } 113 131 } 114 132
+24 -6
src/applications/diviner/publisher/DivinerPublisher.php
··· 8 8 private $renderer; 9 9 private $config; 10 10 private $symbolReverseMap; 11 + private $dropCaches; 12 + 13 + public function setDropCaches($drop_caches) { 14 + $this->dropCaches = $drop_caches; 15 + return $this; 16 + } 11 17 12 18 public function setRenderer(DivinerRenderer $renderer) { 13 19 $renderer->setPublisher($this); ··· 26 32 27 33 public function getConfig($key, $default = null) { 28 34 return idx($this->config, $key, $default); 35 + } 36 + 37 + public function getConfigurationData() { 38 + return $this->config; 29 39 } 30 40 31 41 public function setAtomCache(DivinerAtomCache $cache) { ··· 109 119 final public function publishAtoms(array $hashes) { 110 120 $existing = $this->loadAllPublishedHashes(); 111 121 112 - $existing_map = array_fill_keys($existing, true); 113 - $hashes_map = array_fill_keys($hashes, true); 122 + if ($this->dropCaches) { 123 + $deleted = $existing; 124 + $created = $hashes; 125 + } else { 126 + $existing_map = array_fill_keys($existing, true); 127 + $hashes_map = array_fill_keys($hashes, true); 114 128 115 - $deleted = array_diff_key($existing_map, $hashes_map); 116 - $created = array_diff_key($hashes_map, $existing_map); 129 + $deleted = array_diff_key($existing_map, $hashes_map); 130 + $created = array_diff_key($hashes_map, $existing_map); 131 + 132 + $deleted = array_keys($deleted); 133 + $created = array_keys($created); 134 + } 117 135 118 136 echo pht('Deleting %d documents.', count($deleted))."\n"; 119 - $this->deleteDocumentsByHash(array_keys($deleted)); 137 + $this->deleteDocumentsByHash($deleted); 120 138 121 139 echo pht('Creating %d documents.', count($created))."\n"; 122 - $this->createDocumentsByHash(array_keys($created)); 140 + $this->createDocumentsByHash($created); 123 141 } 124 142 125 143 protected function shouldGenerateDocumentForAtom(DivinerAtom $atom) {
+12
src/applications/diviner/query/DivinerAtomQuery.php
··· 10 10 private $types; 11 11 private $contexts; 12 12 private $indexes; 13 + private $includeUndocumentable; 13 14 14 15 private $needAtoms; 15 16 ··· 50 51 51 52 public function needAtoms($need) { 52 53 $this->needAtoms = $need; 54 + return $this; 55 + } 56 + 57 + public function withIncludeUndocumentable($include) { 58 + $this->includeUndocumentable = $include; 53 59 return $this; 54 60 } 55 61 ··· 180 186 $conn_r, 181 187 'atomIndex IN (%Ld)', 182 188 $this->indexes); 189 + } 190 + 191 + if (!$this->includeUndocumentable) { 192 + $where[] = qsprintf( 193 + $conn_r, 194 + 'isDocumentable = 1'); 183 195 } 184 196 185 197 $where[] = $this->buildPagingClause($conn_r);
+17
src/applications/diviner/storage/DivinerLiveBook.php
··· 6 6 protected $phid; 7 7 protected $name; 8 8 protected $viewPolicy; 9 + protected $configurationData = array(); 9 10 10 11 public function getConfiguration() { 11 12 return array( 12 13 self::CONFIG_AUX_PHID => true, 14 + self::CONFIG_SERIALIZATION => array( 15 + 'configurationData' => self::SERIALIZATION_JSON, 16 + ), 13 17 ) + parent::getConfiguration(); 14 18 } 15 19 20 + public function getConfig($key, $default = null) { 21 + return idx($this->configurationData, $key, $default); 22 + } 23 + 24 + public function setConfig($key, $value) { 25 + $this->configurationData[$key] = $value; 26 + return $this; 27 + } 28 + 16 29 public function generatePHID() { 17 30 return PhabricatorPHID::generateNewPHID( 18 31 PhabricatorPHIDConstants::PHID_TYPE_BOOK); 32 + } 33 + 34 + public function getTitle() { 35 + return $this->getConfig('title', $this->getName()); 19 36 } 20 37 21 38 /* -( PhabricatorPolicyInterface )----------------------------------------- */
+17
src/applications/diviner/storage/DivinerLiveSymbol.php
··· 12 12 protected $graphHash; 13 13 protected $identityHash; 14 14 15 + protected $title; 16 + protected $groupName; 17 + protected $summary; 18 + protected $isDocumentable = 0; 19 + 15 20 private $book; 16 21 private $content; 17 22 private $atom; ··· 80 85 return '/'.implode('/', $parts).'/'; 81 86 } 82 87 88 + public function getSortKey() { 89 + return $this->getTitle(); 90 + } 91 + 83 92 public function save() { 84 93 85 94 // NOTE: The identity hash is just a sanity check because the unique tuple ··· 99 108 } 100 109 101 110 return parent::save(); 111 + } 112 + 113 + public function getTitle() { 114 + $title = parent::getTitle(); 115 + if (!strlen($title)) { 116 + $title = $this->getName(); 117 + } 118 + return $title; 102 119 } 103 120 104 121
+3 -2
src/applications/diviner/workflow/DivinerGenerateWorkflow.php
··· 131 131 $this->buildAtomCache(); 132 132 $this->buildGraphCache(); 133 133 134 - $this->publishDocumentation(); 134 + $this->publishDocumentation($args->getArg('clean')); 135 135 } 136 136 137 137 /* -( Atom Cache )--------------------------------------------------------- */ ··· 439 439 } 440 440 441 441 442 - private function publishDocumentation() { 442 + private function publishDocumentation($clean) { 443 443 $atom_cache = $this->getAtomCache(); 444 444 $graph_map = $atom_cache->getGraphMap(); 445 445 446 446 $this->log(pht('PUBLISHING DOCUMENTATION')); 447 447 448 448 $publisher = new DivinerLivePublisher(); 449 + $publisher->setDropCaches($clean); 449 450 $publisher->setConfig($this->getAllConfig()); 450 451 $publisher->setAtomCache($atom_cache); 451 452 $publisher->setRenderer(new DivinerDefaultRenderer());
+31 -1
src/docs/book/user.book
··· 1 1 { 2 2 "name" : "phabricator", 3 - "root" : "../../../" 3 + "title" : "Phabricator User Documentation", 4 + "root" : "../../../", 5 + "groups" : { 6 + "intro" : { 7 + "name" : "Introduction" 8 + }, 9 + "config" : { 10 + "name" : "Configuration" 11 + }, 12 + "userguide" : { 13 + "name" : "Application User Guides" 14 + }, 15 + "differential" : { 16 + "name" : "Differential (Code Review)" 17 + }, 18 + "diffusion" : { 19 + "name" : "Diffusion (Repository Browser)" 20 + }, 21 + "maniphest" : { 22 + "name" : "Maniphest (Task Tracking)" 23 + }, 24 + "slowvote" : { 25 + "name" : "Slowvote (Polls)" 26 + }, 27 + "herald" : { 28 + "name" : "Herald (Notifications)" 29 + }, 30 + "phriction" : { 31 + "name" : "Phriction (Wiki)" 32 + } 33 + } 4 34 }
+4
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 1342 1342 'type' => 'sql', 1343 1343 'name' => $this->getPatchPath('20130531.filekeys.sql'), 1344 1344 ), 1345 + '20130602.morediviner.sql' => array( 1346 + 'type' => 'sql', 1347 + 'name' => $this->getPatchPath('20130602.morediviner.sql'), 1348 + ), 1345 1349 ); 1346 1350 } 1347 1351 }