@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

Separate editing of package data and paths in Owners

Summary:
Ref T8320. There's currently one enormous form; split it into a general information form (name, description, owners) and a paths form.

I think this is a little more manageable from both a UX point of view and from an "I have to convert this to use ApplicationTransactions" point of view.

Test Plan:
- Edited paths.
- Edited non-path information.
- Created new packages.

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T8320

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

+211 -103
+2
src/__phutil_library_map__.php
··· 2181 2181 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 2182 2182 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', 2183 2183 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 2184 + 'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php', 2184 2185 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 2185 2186 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 2186 2187 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', ··· 5588 5589 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 5589 5590 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', 5590 5591 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 5592 + 'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController', 5591 5593 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 5592 5594 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 5593 5595 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck',
+1
src/applications/owners/application/PhabricatorOwnersApplication.php
··· 47 47 'new/' => 'PhabricatorOwnersEditController', 48 48 'package/(?P<id>[1-9]\d*)/' => 'PhabricatorOwnersDetailController', 49 49 'delete/(?P<id>[1-9]\d*)/' => 'PhabricatorOwnersDeleteController', 50 + 'paths/(?P<id>[1-9]\d*)/' => 'PhabricatorOwnersPathsController', 50 51 ), 51 52 ); 52 53 }
+26 -1
src/applications/owners/controller/PhabricatorOwnersDetailController.php
··· 188 188 189 189 $id = $package->getID(); 190 190 $edit_uri = $this->getApplicationURI("/edit/{$id}/"); 191 + $paths_uri = $this->getApplicationURI("/paths/{$id}/"); 191 192 $delete_uri = $this->getApplicationURI("/delete/{$id}/"); 192 193 193 194 $view = id(new PhabricatorActionListView()) ··· 200 201 ->setDisabled(!$can_edit) 201 202 ->setWorkflow(!$can_edit) 202 203 ->setHref($edit_uri)) 204 + ->addAction( 205 + id(new PhabricatorActionView()) 206 + ->setName(pht('Edit Paths')) 207 + ->setIcon('fa-folder-open') 208 + ->setDisabled(!$can_edit) 209 + ->setWorkflow(!$can_edit) 210 + ->setHref($paths_uri)) 203 211 ->addAction( 204 212 id(new PhabricatorActionView()) 205 213 ->setName(pht('Delete Package')) ··· 242 250 ); 243 251 } 244 252 253 + $info = null; 254 + if (!$paths) { 255 + $info = id(new PHUIInfoView()) 256 + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) 257 + ->setErrors( 258 + array( 259 + pht( 260 + 'This package does not contain any paths yet. Use '. 261 + '"Edit Paths" to add some.'), 262 + )); 263 + } 264 + 245 265 $table = id(new AphrontTableView($rows)) 246 266 ->setHeaders( 247 267 array( ··· 256 276 'wide', 257 277 )); 258 278 259 - return id(new PHUIObjectBoxView()) 279 + $box = id(new PHUIObjectBoxView()) 260 280 ->setHeaderText(pht('Paths')) 261 281 ->appendChild($table); 262 282 283 + if ($info) { 284 + $box->setInfoView($info); 285 + } 286 + 287 + return $box; 263 288 } 264 289 265 290 }
+26 -102
src/applications/owners/controller/PhabricatorOwnersEditController.php
··· 21 21 if (!$package) { 22 22 return new Aphront404Response(); 23 23 } 24 + 25 + $is_new = false; 24 26 } else { 25 27 $package = new PhabricatorOwnersPackage(); 26 28 $package->setPrimaryOwnerPHID($viewer->getPHID()); 29 + 30 + $is_new = true; 27 31 } 28 32 29 33 $e_name = true; ··· 51 55 } 52 56 $owners = array_unique($owners); 53 57 54 - $paths = $request->getArr('path'); 55 - $repos = $request->getArr('repo'); 56 - $excludes = $request->getArr('exclude'); 57 - 58 - $path_refs = array(); 59 - for ($ii = 0; $ii < count($paths); $ii++) { 60 - if (empty($paths[$ii]) || empty($repos[$ii])) { 61 - continue; 62 - } 63 - $path_refs[] = array( 64 - 'repositoryPHID' => $repos[$ii], 65 - 'path' => $paths[$ii], 66 - 'excluded' => $excludes[$ii], 67 - ); 68 - } 69 - 70 58 if (!strlen($package->getName())) { 71 59 $e_name = pht('Required'); 72 60 $errors[] = pht('Package name is required.'); ··· 79 67 $errors[] = pht('Package must have a primary owner.'); 80 68 } else { 81 69 $e_primary = null; 82 - } 83 - 84 - if (!$path_refs) { 85 - $errors[] = pht('Package must include at least one path.'); 86 70 } 87 71 88 72 if (!$errors) { 89 73 $package->attachUnsavedOwners($owners); 90 - $package->attachUnsavedPaths($path_refs); 74 + $package->attachUnsavedPaths(array()); 91 75 $package->attachOldAuditingEnabled($old_auditing_enabled); 92 76 $package->attachOldPrimaryOwnerPHID($old_primary); 93 77 try { ··· 95 79 ->setActor($viewer) 96 80 ->setPackage($package) 97 81 ->save(); 98 - return id(new AphrontRedirectResponse()) 99 - ->setURI('/owners/package/'.$package->getID().'/'); 82 + 83 + $id = $package->getID(); 84 + if ($is_new) { 85 + $next_uri = '/owners/paths/'.$id.'/'; 86 + } else { 87 + $next_uri = '/owners/package/'.$id.'/'; 88 + } 89 + 90 + return id(new AphrontRedirectResponse())->setURI($next_uri); 100 91 } catch (AphrontDuplicateKeyQueryException $ex) { 101 92 $e_name = pht('Duplicate'); 102 93 $errors[] = pht('Package name must be unique.'); ··· 105 96 } else { 106 97 $owners = $package->loadOwners(); 107 98 $owners = mpull($owners, 'getUserPHID'); 108 - 109 - $paths = $package->loadPaths(); 110 - $path_refs = array(); 111 - foreach ($paths as $path) { 112 - $path_refs[] = array( 113 - 'repositoryPHID' => $path->getRepositoryPHID(), 114 - 'path' => $path->getPath(), 115 - 'excluded' => $path->getExcluded(), 116 - ); 117 - } 118 99 } 119 100 120 101 $primary = $package->getPrimaryOwnerPHID(); ··· 124 105 $value_primary_owner = array(); 125 106 } 126 107 127 - if ($package->getID()) { 128 - $title = pht('Edit Package'); 129 - } else { 108 + if ($is_new) { 109 + $cancel_uri = '/owners/'; 130 110 $title = pht('New Package'); 131 - } 132 - 133 - $repos = id(new PhabricatorRepositoryQuery()) 134 - ->setViewer($viewer) 135 - ->execute(); 136 - 137 - $default_paths = array(); 138 - foreach ($repos as $repo) { 139 - $default_path = $repo->getDetail('default-owners-path'); 140 - if ($default_path) { 141 - $default_paths[$repo->getPHID()] = $default_path; 142 - } 111 + $button_text = pht('Continue'); 112 + } else { 113 + $cancel_uri = '/owners/package/'.$package->getID().'/'; 114 + $title = pht('Edit Package'); 115 + $button_text = pht('Save Package'); 143 116 } 144 117 145 - $repos = mpull($repos, 'getCallsign', 'getPHID'); 146 - asort($repos); 147 - 148 - $template = new AphrontTypeaheadTemplateView(); 149 - $template = $template->render(); 150 - 151 - Javelin::initBehavior( 152 - 'owners-path-editor', 153 - array( 154 - 'root' => 'path-editor', 155 - 'table' => 'paths', 156 - 'add_button' => 'addpath', 157 - 'repositories' => $repos, 158 - 'input_template' => $template, 159 - 'pathRefs' => $path_refs, 160 - 161 - 'completeURI' => '/diffusion/services/path/complete/', 162 - 'validateURI' => '/diffusion/services/path/validate/', 163 - 164 - 'repositoryDefaultPaths' => $default_paths, 165 - )); 166 - 167 - require_celerity_resource('owners-path-editor-css'); 168 - 169 - $cancel_uri = $package->getID() 170 - ? '/owners/package/'.$package->getID().'/' 171 - : '/owners/'; 172 - 173 118 $form = id(new AphrontFormView()) 174 119 ->setUser($viewer) 175 120 ->appendChild( ··· 211 156 ? 'enabled' 212 157 : 'disabled')) 213 158 ->appendChild( 214 - id(new PHUIFormInsetView()) 215 - ->setTitle(pht('Paths')) 216 - ->addDivAttributes(array('id' => 'path-editor')) 217 - ->setRightButton(javelin_tag( 218 - 'a', 219 - array( 220 - 'href' => '#', 221 - 'class' => 'button green', 222 - 'sigil' => 'addpath', 223 - 'mustcapture' => true, 224 - ), 225 - pht('Add New Path'))) 226 - ->setDescription( 227 - pht( 228 - 'Specify the files and directories which comprise '. 229 - 'this package.')) 230 - ->setContent(javelin_tag( 231 - 'table', 232 - array( 233 - 'class' => 'owners-path-editor-table', 234 - 'sigil' => 'paths', 235 - ), 236 - ''))) 237 - ->appendChild( 238 159 id(new AphrontFormTextAreaControl()) 239 160 ->setLabel(pht('Description')) 240 161 ->setName('description') ··· 242 163 ->appendChild( 243 164 id(new AphrontFormSubmitControl()) 244 165 ->addCancelButton($cancel_uri) 245 - ->setValue(pht('Save Package'))); 166 + ->setValue($button_text)); 246 167 247 168 $form_box = id(new PHUIObjectBoxView()) 248 169 ->setHeaderText($title) ··· 251 172 252 173 $crumbs = $this->buildApplicationCrumbs(); 253 174 if ($package->getID()) { 254 - $crumbs->addTextCrumb(pht('Edit %s', $package->getName())); 175 + $crumbs->addTextCrumb( 176 + $package->getName(), 177 + $this->getApplicationURI('package/'.$package->getID().'/')); 178 + $crumbs->addTextCrumb(pht('Edit')); 255 179 } else { 256 180 $crumbs->addTextCrumb(pht('New Package')); 257 181 }
+156
src/applications/owners/controller/PhabricatorOwnersPathsController.php
··· 1 + <?php 2 + 3 + final class PhabricatorOwnersPathsController 4 + extends PhabricatorOwnersController { 5 + 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $request->getUser(); 8 + 9 + $package = id(new PhabricatorOwnersPackageQuery()) 10 + ->setViewer($viewer) 11 + ->withIDs(array($request->getURIData('id'))) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + // TODO: Support this capability. 16 + // PhabricatorPolicyCapability::CAN_EDIT, 17 + )) 18 + ->executeOne(); 19 + if (!$package) { 20 + return new Aphront404Response(); 21 + } 22 + 23 + if ($request->isFormPost()) { 24 + $paths = $request->getArr('path'); 25 + $repos = $request->getArr('repo'); 26 + $excludes = $request->getArr('exclude'); 27 + 28 + $path_refs = array(); 29 + for ($ii = 0; $ii < count($paths); $ii++) { 30 + if (empty($paths[$ii]) || empty($repos[$ii])) { 31 + continue; 32 + } 33 + $path_refs[] = array( 34 + 'repositoryPHID' => $repos[$ii], 35 + 'path' => $paths[$ii], 36 + 'excluded' => $excludes[$ii], 37 + ); 38 + } 39 + 40 + $package->attachUnsavedOwners(array()); 41 + $package->attachUnsavedPaths($path_refs); 42 + $package->attachOldAuditingEnabled($package->getAuditingEnabled()); 43 + $package->attachOldPrimaryOwnerPHID($package->getPrimaryOwnerPHID()); 44 + 45 + id(new PhabricatorOwnersPackageEditor()) 46 + ->setActor($viewer) 47 + ->setPackage($package) 48 + ->save(); 49 + 50 + return id(new AphrontRedirectResponse()) 51 + ->setURI('/owners/package/'.$package->getID().'/'); 52 + } else { 53 + $paths = $package->loadPaths(); 54 + $path_refs = array(); 55 + foreach ($paths as $path) { 56 + $path_refs[] = array( 57 + 'repositoryPHID' => $path->getRepositoryPHID(), 58 + 'path' => $path->getPath(), 59 + 'excluded' => $path->getExcluded(), 60 + ); 61 + } 62 + } 63 + 64 + $repos = id(new PhabricatorRepositoryQuery()) 65 + ->setViewer($viewer) 66 + ->execute(); 67 + 68 + $default_paths = array(); 69 + foreach ($repos as $repo) { 70 + $default_path = $repo->getDetail('default-owners-path'); 71 + if ($default_path) { 72 + $default_paths[$repo->getPHID()] = $default_path; 73 + } 74 + } 75 + 76 + $repos = mpull($repos, 'getCallsign', 'getPHID'); 77 + asort($repos); 78 + 79 + $template = new AphrontTypeaheadTemplateView(); 80 + $template = $template->render(); 81 + 82 + Javelin::initBehavior( 83 + 'owners-path-editor', 84 + array( 85 + 'root' => 'path-editor', 86 + 'table' => 'paths', 87 + 'add_button' => 'addpath', 88 + 'repositories' => $repos, 89 + 'input_template' => $template, 90 + 'pathRefs' => $path_refs, 91 + 92 + 'completeURI' => '/diffusion/services/path/complete/', 93 + 'validateURI' => '/diffusion/services/path/validate/', 94 + 95 + 'repositoryDefaultPaths' => $default_paths, 96 + )); 97 + 98 + require_celerity_resource('owners-path-editor-css'); 99 + 100 + $cancel_uri = '/owners/package/'.$package->getID().'/'; 101 + 102 + $form = id(new AphrontFormView()) 103 + ->setUser($viewer) 104 + ->appendChild( 105 + id(new PHUIFormInsetView()) 106 + ->setTitle(pht('Paths')) 107 + ->addDivAttributes(array('id' => 'path-editor')) 108 + ->setRightButton(javelin_tag( 109 + 'a', 110 + array( 111 + 'href' => '#', 112 + 'class' => 'button green', 113 + 'sigil' => 'addpath', 114 + 'mustcapture' => true, 115 + ), 116 + pht('Add New Path'))) 117 + ->setDescription( 118 + pht( 119 + 'Specify the files and directories which comprise '. 120 + 'this package.')) 121 + ->setContent(javelin_tag( 122 + 'table', 123 + array( 124 + 'class' => 'owners-path-editor-table', 125 + 'sigil' => 'paths', 126 + ), 127 + ''))) 128 + ->appendChild( 129 + id(new AphrontFormSubmitControl()) 130 + ->addCancelButton($cancel_uri) 131 + ->setValue(pht('Save Paths'))); 132 + 133 + $form_box = id(new PHUIObjectBoxView()) 134 + ->setHeaderText(pht('Edit Paths')) 135 + ->setForm($form); 136 + 137 + $crumbs = $this->buildApplicationCrumbs(); 138 + $crumbs->addTextCrumb( 139 + $package->getName(), 140 + $this->getApplicationURI('package/'.$package->getID().'/')); 141 + $crumbs->addTextCrumb(pht('Edit Paths')); 142 + 143 + return $this->buildApplicationPage( 144 + array( 145 + $crumbs, 146 + $form_box, 147 + ), 148 + array( 149 + 'title' => array( 150 + $package->getName(), 151 + pht('Edit Paths'), 152 + ), 153 + )); 154 + } 155 + 156 + }