diff --git a/resources/sql/patches/018.owners.sql b/resources/sql/patches/018.owners.sql index b2bc59ece8..9628cc3710 100644 --- a/resources/sql/patches/018.owners.sql +++ b/resources/sql/patches/018.owners.sql @@ -1,6 +1,6 @@ CREATE DATABASE phabricator_owners; -CREATE TABLE phabricator_owners.onwners_package ( +CREATE TABLE phabricator_owners.owners_package ( id int unsigned not null auto_increment primary key, phid varchar(64) binary not null, unique key(phid), @@ -23,6 +23,5 @@ CREATE TABLE phabricator_owners.owners_path ( packageID int unsigned not null, key(packageID), repositoryPHID varchar(64) binary not null, - path varchar(255) not null, - unique key (repositoryPHID, path) + path varchar(255) not null ); \ No newline at end of file diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 758a39c077..5c3fcfee6c 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -63,7 +63,7 @@ celerity_register_resource_map(array( ), 'aphront-list-filter-view-css' => array( - 'uri' => '/res/89f641c5/rsrc/css/aphront/list-filter-view.css', + 'uri' => '/res/50a790ae/rsrc/css/aphront/list-filter-view.css', 'type' => 'css', 'requires' => array( @@ -81,7 +81,7 @@ celerity_register_resource_map(array( ), 'aphront-panel-view-css' => array( - 'uri' => '/res/63672373/rsrc/css/aphront/panel-view.css', + 'uri' => '/res/8f9f3632/rsrc/css/aphront/panel-view.css', 'type' => 'css', 'requires' => array( @@ -643,7 +643,7 @@ celerity_register_resource_map(array( ), array ( 'packages' => array ( - 'e3ec35d7' => + '2de9aa4e' => array ( 'name' => 'core.pkg.css', 'symbols' => @@ -663,7 +663,7 @@ celerity_register_resource_map(array( 12 => 'phabricator-remarkup-css', 13 => 'syntax-highlighting-css', ), - 'uri' => '/res/pkg/e3ec35d7/core.pkg.css', + 'uri' => '/res/pkg/2de9aa4e/core.pkg.css', 'type' => 'css', ), '76f3c1f8' => @@ -710,20 +710,20 @@ celerity_register_resource_map(array( ), 'reverse' => array ( - 'phabricator-core-css' => 'e3ec35d7', - 'phabricator-core-buttons-css' => 'e3ec35d7', - 'phabricator-standard-page-view' => 'e3ec35d7', - 'aphront-dialog-view-css' => 'e3ec35d7', - 'aphront-form-view-css' => 'e3ec35d7', - 'aphront-panel-view-css' => 'e3ec35d7', - 'aphront-side-nav-view-css' => 'e3ec35d7', - 'aphront-table-view-css' => 'e3ec35d7', - 'aphront-crumbs-view-css' => 'e3ec35d7', - 'aphront-tokenizer-control-css' => 'e3ec35d7', - 'aphront-typeahead-control-css' => 'e3ec35d7', - 'phabricator-directory-css' => 'e3ec35d7', - 'phabricator-remarkup-css' => 'e3ec35d7', - 'syntax-highlighting-css' => 'e3ec35d7', + 'phabricator-core-css' => '2de9aa4e', + 'phabricator-core-buttons-css' => '2de9aa4e', + 'phabricator-standard-page-view' => '2de9aa4e', + 'aphront-dialog-view-css' => '2de9aa4e', + 'aphront-form-view-css' => '2de9aa4e', + 'aphront-panel-view-css' => '2de9aa4e', + 'aphront-side-nav-view-css' => '2de9aa4e', + 'aphront-table-view-css' => '2de9aa4e', + 'aphront-crumbs-view-css' => '2de9aa4e', + 'aphront-tokenizer-control-css' => '2de9aa4e', + 'aphront-typeahead-control-css' => '2de9aa4e', + 'phabricator-directory-css' => '2de9aa4e', + 'phabricator-remarkup-css' => '2de9aa4e', + 'syntax-highlighting-css' => '2de9aa4e', 'differential-core-view-css' => '76f3c1f8', 'differential-changeset-view-css' => '76f3c1f8', 'differential-revision-detail-css' => '76f3c1f8', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index e8d4a40a1d..21be35b244 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -320,7 +320,9 @@ phutil_register_library_map(array( 'PhabricatorObjectSelectorDialog' => 'view/control/objectselector', 'PhabricatorOwnersController' => 'applications/owners/controller/base', 'PhabricatorOwnersDAO' => 'applications/owners/storage/base', + 'PhabricatorOwnersDeleteController' => 'applications/owners/controller/delete', 'PhabricatorOwnersDetailController' => 'applications/owners/controller/detail', + 'PhabricatorOwnersEditController' => 'applications/owners/controller/edit', 'PhabricatorOwnersListController' => 'applications/owners/controller/list', 'PhabricatorOwnersOwner' => 'applications/owners/storage/owner', 'PhabricatorOwnersPackage' => 'applications/owners/storage/package', @@ -677,7 +679,9 @@ phutil_register_library_map(array( 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 'PhabricatorOwnersController' => 'PhabricatorController', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', + 'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', + 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', 'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPackage' => 'PhabricatorOwnersDAO', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 59819b28ec..42cd52cb75 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -260,8 +260,10 @@ class AphrontDefaultApplicationConfiguration '/owners/' => array( '$' => 'PhabricatorOwnersListController', 'view/(?P[^/]+)/$' => 'PhabricatorOwnersListController', + 'edit/(?P\d+)/$' => 'PhabricatorOwnersEditController', + 'new/$' => 'PhabricatorOwnersEditController', 'package/(?P\d+)/$' => 'PhabricatorOwnersDetailController', - 'new/$' => 'PhabricatorOwnersDetailController', + 'delete/(?P\d+)/$' => 'PhabricatorOwnersDeleteController', ), ); diff --git a/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php b/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php index ccb5fbfed0..4536a643cd 100644 --- a/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php +++ b/src/applications/diffusion/controller/pathcomplete/DiffusionPathCompleteController.php @@ -47,8 +47,7 @@ class DiffusionPathCompleteController extends DiffusionController { $drequest = DiffusionRequest::newFromAphrontRequestDictionary( array( 'callsign' => $repository->getCallsign(), - 'path' => $query_dir, - 'nobranch' => true, + 'path' => ':/'.$query_dir, )); $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); diff --git a/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php b/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php index f57cc6cc15..6199cdca2b 100644 --- a/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php +++ b/src/applications/diffusion/controller/pathvalidate/DiffusionPathValidateController.php @@ -39,8 +39,7 @@ class DiffusionPathValidateController extends DiffusionController { $drequest = DiffusionRequest::newFromAphrontRequestDictionary( array( 'callsign' => $repository->getCallsign(), - 'path' => $path, - 'nobranch' => true, + 'path' => ':/'.$path, )); $browse_query = DiffusionBrowseQuery::newFromDiffusionRequest($drequest); diff --git a/src/applications/diffusion/request/git/DiffusionGitRequest.php b/src/applications/diffusion/request/git/DiffusionGitRequest.php index 3ac4e8aa32..11e414a379 100644 --- a/src/applications/diffusion/request/git/DiffusionGitRequest.php +++ b/src/applications/diffusion/request/git/DiffusionGitRequest.php @@ -24,8 +24,8 @@ class DiffusionGitRequest extends DiffusionRequest { $path = $this->path; $parts = explode('/', $path); - if (empty($data['nobranch'])) { - $branch = array_shift($parts); + $branch = array_shift($parts); + if ($branch != ':') { $this->branch = $this->decodeBranchName($branch); } diff --git a/src/applications/diffusion/request/svn/DiffusionSvnRequest.php b/src/applications/diffusion/request/svn/DiffusionSvnRequest.php index fb6c1bae53..dea97cb9f4 100644 --- a/src/applications/diffusion/request/svn/DiffusionSvnRequest.php +++ b/src/applications/diffusion/request/svn/DiffusionSvnRequest.php @@ -20,6 +20,14 @@ class DiffusionSvnRequest extends DiffusionRequest { private $loadedCommit; + protected function initializeFromAphrontRequestDictionary(array $data) { + parent::initializeFromAphrontRequestDictionary($data); + if (!strncmp($this->path, ':', 1)) { + $this->path = substr($this->path, 1); + $this->path = ltrim($this->path, '/'); + } + } + public function getCommit() { if ($this->commit) { return $this->commit; diff --git a/src/applications/owners/controller/delete/PhabricatorOwnersDeleteController.php b/src/applications/owners/controller/delete/PhabricatorOwnersDeleteController.php new file mode 100644 index 0000000000..2e4ad2e8d1 --- /dev/null +++ b/src/applications/owners/controller/delete/PhabricatorOwnersDeleteController.php @@ -0,0 +1,55 @@ +id = $data['id']; + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + $package = id(new PhabricatorOwnersPackage())->load($this->id); + if (!$package) { + return new Aphront404Response(); + } + + if ($request->isDialogFormPost()) { + $package->delete(); + return id(new AphrontRedirectResponse())->setURI('/owners/'); + } + + $dialog = id(new AphrontDialogView()) + ->setUser($user) + ->setTitle('Really delete this package?') + ->appendChild( + '

Are you sure you want to delete the "'. + phutil_escape_html($package->getName()).'" package? This operation '. + 'can not be undone.

') + ->addSubmitButton('Delete') + ->addCancelButton('/owners/package/'.$package->getID().'/') + ->setSubmitURI($request->getRequestURI()); + + return id(new AphrontDialogResponse())->setDialog($dialog); + } + +} diff --git a/src/applications/owners/controller/delete/__init__.php b/src/applications/owners/controller/delete/__init__.php new file mode 100644 index 0000000000..14c5c7834c --- /dev/null +++ b/src/applications/owners/controller/delete/__init__.php @@ -0,0 +1,19 @@ +id = idx($data, 'id'); + $this->id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); - if ($this->id) { - $package = id(new PhabricatorOwnersPackage())->load($this->id); - if (!$package) { - return new Aphront404Response(); - } - } else { - $package = new PhabricatorOwnersPackage(); - $package->setPrimaryOwnerPHID($user->getPHID()); + $package = id(new PhabricatorOwnersPackage())->load($this->id); + if (!$package) { + return new Aphront404Response(); } - $e_name = true; - $e_primary = true; + $paths = $package->loadPaths(); + $owners = $package->loadOwners(); - - $token_primary_owner = array(); - $token_all_owners = array(); - - $title = $package->getID() ? 'Edit Package' : 'New Package'; - - $repos = id(new PhabricatorRepository())->loadAll(); - - $default_paths = array(); - foreach ($repos as $repo) { - $default_path = $repo->getDetail('default-owners-path'); - if ($default_path) { - $default_paths[$repo->getPHID()] = $default_path; - } + $phids = array(); + foreach ($paths as $path) { + $phids[$path->getRepositoryPHID()] = true; } + foreach ($owners as $owner) { + $phids[$owner->getUserPHID()] = true; + } + $phids = array_keys($phids); - $repos = mpull($repos, 'getCallsign', 'getPHID'); + $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); - $template = new AphrontTypeaheadTemplateView(); - $template = $template->render(); + $rows = array(); + $rows[] = array( + 'Name', + phutil_escape_html($package->getName())); + $rows[] = array( + 'Description', + phutil_escape_html($package->getDescription())); - Javelin::initBehavior( - 'owners-path-editor', + $primary_owner = null; + $primary_phid = $package->getPrimaryOwnerPHID(); + if ($primary_phid && isset($handles[$primary_phid])) { + $primary_owner = + ''.$handles[$primary_phid]->renderLink().''; + } + $rows[] = array( + 'Primary Owner', + $primary_owner, + ); + + $owner_links = array(); + foreach ($owners as $owner) { + $owner_links[] = $handles[$owner->getUserPHID()]->renderLink(); + } + $owner_links = implode('
', $owner_links); + $rows[] = array( + 'Owners', + $owner_links); + + $path_links = array(); + foreach ($paths as $path) { + $callsign = $handles[$path->getRepositoryPHID()]->getName(); + $repo = phutil_escape_html('r'.$callsign); + $path_link = phutil_render_tag( + 'a', + array( + 'href' => '/diffusion/'.$callsign.'/browse/:'.$path->getPath(), + ), + phutil_escape_html($path->getPath())); + $path_links[] = $repo.' '.$path_link; + } + $path_links = implode('
', $path_links); + $rows[] = array( + 'Paths', + $path_links); + + $table = new AphrontTableView($rows); + $table->setColumnClasses( array( - 'root' => 'path-editor', - 'table' => 'paths', - 'add_button' => 'addpath', - 'repositories' => $repos, - 'input_template' => $template, - 'path_refs' => array(), - - 'completeURI' => '/diffusion/services/path/complete/', - 'validateURI' => '/diffusion/services/path/validate/', - - 'repositoryDefaultPaths' => $default_paths, + 'header', + 'wide', )); - require_celerity_resource('owners-path-editor-css'); - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel('Name') - ->setName('name') - ->setValue($package->getName()) - ->setError($e_name)) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setDatasource('/typeahead/common/users/') - ->setLabel('Primary Owner') - ->setName('primary') - ->setLimit(1) - ->setValue($token_primary_owner) - ->setError($e_primary)) - ->appendChild( - id(new AphrontFormTokenizerControl()) - ->setDatasource('/typeahead/common/users/') - ->setLabel('Owners') - ->setName('owners') - ->setValue($token_all_owners) - ->setError($e_primary)) - ->appendChild( - '

Paths

'. - '
'. - '
'. - javelin_render_tag( - 'a', - array( - 'href' => '#', - 'class' => 'button green', - 'sigil' => 'addpath', - 'mustcapture' => true, - ), - 'Add New Path'). - '
'. - '

Specify the files and directories which comprise this '. - 'package.

'. - '
'. - javelin_render_tag( - 'table', - array( - 'class' => 'owners-path-editor-table', - 'sigil' => 'paths', - ), - ''). - '
') - ->appendChild( - id(new AphrontFormTextAreaControl()) - ->setLabel('Description') - ->setName('description') - ->setValue($package->getDescription())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue('Save Package')); - $panel = new AphrontPanelView(); - $panel->setHeader($title); - $panel->setWidth(AphrontPanelView::WIDTH_WIDE); - $panel->appendChild($form); + $panel->setHeader( + 'Package Details for "'.phutil_escape_html($package->getName()).'"'); + $panel->addButton( + javelin_render_tag( + 'a', + array( + 'href' => '/owners/delete/'.$package->getID().'/', + 'class' => 'button grey', + 'sigil' => 'workflow', + ), + 'Delete Package')); + $panel->addButton( + phutil_render_tag( + 'a', + array( + 'href' => '/owners/edit/'.$package->getID().'/', + 'class' => 'button', + ), + 'Edit Package')); + $panel->appendChild($table); + + $nav = new AphrontSideNavView(); + $nav->appendChild($panel); + $nav->addNavItem( + phutil_render_tag( + 'a', + array( + 'href' => '/owners/package/'.$package->getID().'/', + 'class' => 'aphront-side-nav-selected', + ), + 'Package Details')); return $this->buildStandardPageResponse( - $panel, + $nav, array( - 'title' => $title, + 'title' => "Package '".$package->getName()."'", )); } diff --git a/src/applications/owners/controller/detail/__init__.php b/src/applications/owners/controller/detail/__init__.php index 4a02ae3b99..f81c2c268e 100644 --- a/src/applications/owners/controller/detail/__init__.php +++ b/src/applications/owners/controller/detail/__init__.php @@ -9,14 +9,13 @@ phutil_require_module('phabricator', 'aphront/response/404'); phutil_require_module('phabricator', 'applications/owners/controller/base'); phutil_require_module('phabricator', 'applications/owners/storage/package'); -phutil_require_module('phabricator', 'applications/repository/storage/repository'); -phutil_require_module('phabricator', 'infrastructure/celerity/api'); -phutil_require_module('phabricator', 'infrastructure/javelin/api'); -phutil_require_module('phabricator', 'view/control/typeahead'); -phutil_require_module('phabricator', 'view/form/base'); -phutil_require_module('phabricator', 'view/form/control/submit'); +phutil_require_module('phabricator', 'applications/phid/handle/data'); +phutil_require_module('phabricator', 'infrastructure/javelin/markup'); +phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/layout/panel'); +phutil_require_module('phabricator', 'view/layout/sidenav'); +phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'utils'); diff --git a/src/applications/owners/controller/edit/PhabricatorOwnersEditController.php b/src/applications/owners/controller/edit/PhabricatorOwnersEditController.php new file mode 100644 index 0000000000..7de183d4ea --- /dev/null +++ b/src/applications/owners/controller/edit/PhabricatorOwnersEditController.php @@ -0,0 +1,258 @@ +id = idx($data, 'id'); + } + + public function processRequest() { + $request = $this->getRequest(); + $user = $request->getUser(); + + if ($this->id) { + $package = id(new PhabricatorOwnersPackage())->load($this->id); + if (!$package) { + return new Aphront404Response(); + } + } else { + $package = new PhabricatorOwnersPackage(); + $package->setPrimaryOwnerPHID($user->getPHID()); + } + + $e_name = true; + $e_primary = true; + $e_owners = true; + + $errors = array(); + + if ($request->isFormPost()) { + $package->setName($request->getStr('name')); + $package->setDescription($request->getStr('description')); + + $primary = $request->getArr('primary'); + $primary = reset($primary); + $package->setPrimaryOwnerPHID($primary); + + $owners = $request->getArr('owners'); + if ($primary) { + array_unshift($owners, $primary); + } + $owners = array_unique($owners); + + $paths = $request->getArr('path'); + $repos = $request->getArr('repo'); + + $path_refs = array(); + for ($ii = 0; $ii < count($paths); $ii++) { + if (empty($paths[$ii]) || empty($repos[$ii])) { + continue; + } + $path_refs[] = array( + 'repositoryPHID' => $repos[$ii], + 'path' => $paths[$ii], + ); + } + + if (!strlen($package->getName())) { + $e_name = 'Required'; + $errors[] = 'Package name is required.'; + } else { + $e_name = null; + } + + if (!$package->getPrimaryOwnerPHID()) { + $e_primary = 'Required'; + $errors[] = 'Package must have a primary owner.'; + } else { + $e_primary = null; + } + + if (!$owners) { + $e_owners = 'Required'; + $errors[] = 'Package must have at least one owner.'; + } else { + $e_owners = null; + } + + if (!$path_refs) { + $errors[] = 'Package must include at least one path.'; + } + + if (!$errors) { + $package->attachUnsavedOwners($owners); + $package->attachUnsavedPaths($path_refs); + try { + $package->save(); + return id(new AphrontRedirectResponse()) + ->setURI('/owners/package/'.$package->getID().'/'); + } catch (AphrontQueryDuplicateKeyException $ex) { + $e_name = 'Duplicate'; + $errors[] = 'Package name must be unique.'; + } + } + } else { + $owners = $package->loadOwners(); + $owners = mpull($owners, 'getUserPHID'); + + $paths = $package->loadPaths(); + $path_refs = array(); + foreach ($paths as $path) { + $path_refs[] = array( + 'repositoryPHID' => $path->getRepositoryPHID(), + 'path' => $path->getPath(), + ); + } + } + + $error_view = null; + if ($errors) { + $error_view = new AphrontErrorView(); + $error_view->setTitle('Package Errors'); + $error_view->setErrors($errors); + } + + $handles = id(new PhabricatorObjectHandleData($owners)) + ->loadHandles(); + + $primary = $package->getPrimaryOwnerPHID(); + if ($primary && isset($handles[$primary])) { + $token_primary_owner = array( + $primary => $handles[$primary]->getFullName(), + ); + } else { + $token_primary_owner = array(); + } + + $token_all_owners = array_select_keys($handles, $owners); + $token_all_owners = mpull($token_all_owners, 'getFullName'); + + $title = $package->getID() ? 'Edit Package' : 'New Package'; + + $repos = id(new PhabricatorRepository())->loadAll(); + + $default_paths = array(); + foreach ($repos as $repo) { + $default_path = $repo->getDetail('default-owners-path'); + if ($default_path) { + $default_paths[$repo->getPHID()] = $default_path; + } + } + + $repos = mpull($repos, 'getCallsign', 'getPHID'); + + $template = new AphrontTypeaheadTemplateView(); + $template = $template->render(); + + Javelin::initBehavior( + 'owners-path-editor', + array( + 'root' => 'path-editor', + 'table' => 'paths', + 'add_button' => 'addpath', + 'repositories' => $repos, + 'input_template' => $template, + 'pathRefs' => $path_refs, + + 'completeURI' => '/diffusion/services/path/complete/', + 'validateURI' => '/diffusion/services/path/validate/', + + 'repositoryDefaultPaths' => $default_paths, + )); + + require_celerity_resource('owners-path-editor-css'); + + $cancel_uri = $package->getID() + ? '/owners/package/'.$package->getID().'/' + : '/owners/'; + + $form = id(new AphrontFormView()) + ->setUser($user) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Name') + ->setName('name') + ->setValue($package->getName()) + ->setError($e_name)) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/users/') + ->setLabel('Primary Owner') + ->setName('primary') + ->setLimit(1) + ->setValue($token_primary_owner) + ->setError($e_primary)) + ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/users/') + ->setLabel('Owners') + ->setName('owners') + ->setValue($token_all_owners) + ->setError($e_owners)) + ->appendChild( + '

Paths

'. + '
'. + '
'. + javelin_render_tag( + 'a', + array( + 'href' => '#', + 'class' => 'button green', + 'sigil' => 'addpath', + 'mustcapture' => true, + ), + 'Add New Path'). + '
'. + '

Specify the files and directories which comprise this '. + 'package.

'. + '
'. + javelin_render_tag( + 'table', + array( + 'class' => 'owners-path-editor-table', + 'sigil' => 'paths', + ), + ''). + '
') + ->appendChild( + id(new AphrontFormTextAreaControl()) + ->setLabel('Description') + ->setName('description') + ->setValue($package->getDescription())) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue('Save Package')); + + $panel = new AphrontPanelView(); + $panel->setHeader($title); + $panel->setWidth(AphrontPanelView::WIDTH_WIDE); + $panel->appendChild($error_view); + $panel->appendChild($form); + + return $this->buildStandardPageResponse( + $panel, + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/owners/controller/edit/__init__.php b/src/applications/owners/controller/edit/__init__.php new file mode 100644 index 0000000000..6b9054146e --- /dev/null +++ b/src/applications/owners/controller/edit/__init__.php @@ -0,0 +1,26 @@ +view) { case 'search': - $content = $this->renderPackageTable(array(), 'Search Results'); + $packages = array(); + + $header = 'Search Results'; + $nodata = 'No packages match your query.'; break; case 'owned': - $content = $this->renderOwnedView(); + $owner = new PhabricatorOwnersOwner(); + $data = queryfx_all( + $package->establishConnection('r'), + 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID + WHERE o.userPHID = %s GROUP BY p.id', + $package->getTableName(), + $owner->getTableName(), + $user->getPHID()); + $packages = $package->loadAllFromArray($data); + + $header = 'Owned Packages'; + $nodata = 'No owned packages'; break; case 'all': - $content = $this->renderAllView(); + $packages = $package->loadAll(); + + $header = 'All Packages'; + $nodata = 'There are no defined packages.'; break; } + $content = $this->renderPackageTable( + $packages, + $header, + $nodata); + $filter = new AphrontListFilterView(); $filter->addButton( phutil_render_tag( @@ -127,26 +151,68 @@ class PhabricatorOwnersListController extends PhabricatorOwnersController { )); } - private function renderOwnedView() { - $packages = array(); + private function renderPackageTable(array $packages, $header, $nodata) { - return $this->renderPackageTable($packages, 'Owned Packages'); - } + if ($packages) { + $package_ids = mpull($packages, 'getID'); - private function renderAllView() { - $packages = array(); + $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( + 'packageID IN (%Ld)', + $package_ids); - return $this->renderPackageTable($packages, 'All Packages'); - } + $paths = id(new PhabricatorOwnersPath())->loadAllWhere( + 'packageID in (%Ld)', + $package_ids); - private function renderPackageTable(array $packages, $header) { + $phids = array(); + foreach ($owners as $owner) { + $phids[$owner->getUserPHID()] = true; + } + foreach ($paths as $path) { + $phids[$path->getRepositoryPHID()] = true; + } + $phids = array_keys($phids); + + $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); + + $owners = mgroup($owners, 'getPackageID'); + $paths = mgroup($paths, 'getPackageID'); + } else { + $handles = array(); + $owners = array(); + $paths = array(); + } $rows = array(); foreach ($packages as $package) { + + $pkg_owners = idx($owners, $package->getID(), array()); + foreach ($pkg_owners as $key => $owner) { + $pkg_owners[$key] = $handles[$owner->getUserPHID()]->renderLink(); + if ($owner->getUserPHID() == $package->getPrimaryOwnerPHID()) { + $pkg_owners[$key] = ''.$pkg_owners[$key].''; + } + } + $pkg_owners = implode('
', $pkg_owners); + + $pkg_paths = idx($paths, $package->getID(), array()); + foreach ($pkg_paths as $key => $path) { + $repo = $handles[$path->getRepositoryPHID()]->getName(); + $pkg_paths[$key] = + ''.$repo.' '. + phutil_escape_html($path->getPath()); + } + $pkg_paths = implode('
', $pkg_paths); + $rows[] = array( - 'x', - 'y', - 'z', + phutil_render_tag( + 'a', + array( + 'href' => '/owners/package/'.$package->getID().'/', + ), + phutil_escape_html($package->getName())), + $pkg_owners, + $pkg_paths, ); } @@ -159,7 +225,7 @@ class PhabricatorOwnersListController extends PhabricatorOwnersController { )); $table->setColumnClasses( array( - '', + 'pri', '', 'wide wrap', )); diff --git a/src/applications/owners/controller/list/__init__.php b/src/applications/owners/controller/list/__init__.php index e6920b2e11..6a75d3c690 100644 --- a/src/applications/owners/controller/list/__init__.php +++ b/src/applications/owners/controller/list/__init__.php @@ -7,7 +7,11 @@ phutil_require_module('phabricator', 'applications/owners/controller/base'); +phutil_require_module('phabricator', 'applications/owners/storage/owner'); +phutil_require_module('phabricator', 'applications/owners/storage/package'); +phutil_require_module('phabricator', 'applications/owners/storage/path'); phutil_require_module('phabricator', 'applications/phid/handle/data'); +phutil_require_module('phabricator', 'storage/queryfx'); phutil_require_module('phabricator', 'view/control/table'); phutil_require_module('phabricator', 'view/form/base'); phutil_require_module('phabricator', 'view/form/control/submit'); diff --git a/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php b/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php index 3f3c46cfde..8c9d9827cf 100644 --- a/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php +++ b/src/applications/owners/storage/owner/PhabricatorOwnersOwner.php @@ -24,7 +24,7 @@ class PhabricatorOwnersOwner extends PhabricatorOwnersDAO { public function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, - ); + ) + parent::getConfiguration(); } } diff --git a/src/applications/owners/storage/package/PhabricatorOwnersPackage.php b/src/applications/owners/storage/package/PhabricatorOwnersPackage.php index ca96f9c74f..a720cb0888 100644 --- a/src/applications/owners/storage/package/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/package/PhabricatorOwnersPackage.php @@ -23,16 +23,110 @@ class PhabricatorOwnersPackage extends PhabricatorOwnersDAO { protected $description; protected $primaryOwnerPHID; + private $unsavedOwners; + private $unsavedPaths; + public function getConfiguration() { return array( // This information is better available from the history table. self::CONFIG_TIMESTAMPS => false, self::CONFIG_AUX_PHID => true, - ); + ) + parent::getConfiguration(); } public function generatePHID() { - return PhabricatorPHID::generateNew('OPKG'); + return PhabricatorPHID::generateNewPHID('OPKG'); + } + + public function attachUnsavedOwners(array $owners) { + $this->unsavedOwners = $owners; + return $this; + } + + public function attachUnsavedPaths(array $paths) { + $this->unsavedPaths = $paths; + return $this; + } + + public function loadOwners() { + if (!$this->getID()) { + return array(); + } + return id(new PhabricatorOwnersOwner())->loadAllWhere( + 'packageID = %d', + $this->getID()); + } + + public function loadPaths() { + if (!$this->getID()) { + return array(); + } + return id(new PhabricatorOwnersPath())->loadAllWhere( + 'packageID = %d', + $this->getID()); + } + + public function save() { + + // TODO: Transactions! + + $ret = parent::save(); + + if ($this->unsavedOwners) { + $new_owners = array_fill_keys($this->unsavedOwners, true); + $cur_owners = array(); + foreach ($this->loadOwners() as $owner) { + if (empty($new_owners[$owner->getUserPHID()])) { + $owner->delete(); + continue; + } + $cur_owners[$owner->getUserPHID()] = true; + } + $add_owners = array_diff_key($new_owners, $cur_owners); + foreach ($add_owners as $phid => $ignored) { + $owner = new PhabricatorOwnersOwner(); + $owner->setPackageID($this->getID()); + $owner->setUserPHID($phid); + $owner->save(); + } + unset($this->unsavedOwners); + } + + if ($this->unsavedPaths) { + $new_paths = igroup($this->unsavedPaths, 'repositoryPHID', 'path'); + $cur_paths = $this->loadPaths(); + foreach ($cur_paths as $key => $path) { + if (empty($new_paths[$path->getRepositoryPHID()][$path->getPath()])) { + $path->delete(); + unset($cur_paths[$key]); + } + } + $cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath'); + foreach ($new_paths as $repository_phid => $paths) { + foreach ($paths as $path => $ignored) { + if (empty($cur_paths[$repository_phid][$path])) { + $obj = new PhabricatorOwnersPath(); + $obj->setPackageID($this->getID()); + $obj->setRepositoryPHID($repository_phid); + $obj->setPath($path); + $obj->save(); + } + } + } + unset($this->unsavedPaths); + } + + return $ret; + } + + public function delete() { + foreach ($this->loadOwners() as $owner) { + $owner->delete(); + } + foreach ($this->loadPaths() as $path) { + $path->delete(); + } + return parent::delete(); } } diff --git a/src/applications/owners/storage/package/__init__.php b/src/applications/owners/storage/package/__init__.php index 8a401ec1b2..fb10ed296a 100644 --- a/src/applications/owners/storage/package/__init__.php +++ b/src/applications/owners/storage/package/__init__.php @@ -7,7 +7,11 @@ phutil_require_module('phabricator', 'applications/owners/storage/base'); +phutil_require_module('phabricator', 'applications/owners/storage/owner'); +phutil_require_module('phabricator', 'applications/owners/storage/path'); phutil_require_module('phabricator', 'applications/phid/storage/phid'); +phutil_require_module('phutil', 'utils'); + phutil_require_source('PhabricatorOwnersPackage.php'); diff --git a/src/applications/owners/storage/path/PhabricatorOwnersPath.php b/src/applications/owners/storage/path/PhabricatorOwnersPath.php index ce4e055d0d..cf8fbca3ee 100644 --- a/src/applications/owners/storage/path/PhabricatorOwnersPath.php +++ b/src/applications/owners/storage/path/PhabricatorOwnersPath.php @@ -25,7 +25,7 @@ class PhabricatorOwnersPath extends PhabricatorOwnersDAO { public function getConfiguration() { return array( self::CONFIG_TIMESTAMPS => false, - ); + ) + parent::getConfiguration(); } } diff --git a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php index 8c3a9600bb..cc395d316e 100644 --- a/src/applications/phid/handle/data/PhabricatorObjectHandleData.php +++ b/src/applications/phid/handle/data/PhabricatorObjectHandleData.php @@ -201,6 +201,28 @@ class PhabricatorObjectHandleData { $handles[$phid] = $handle; } break; + case PhabricatorPHIDConstants::PHID_TYPE_REPO: + $class = 'PhabricatorRepository'; + PhutilSymbolLoader::loadClass($class); + $object = newv($class, array()); + + $repositories = $object->loadAllWhere('phid in (%Ls)', $phids); + $repositories = mpull($repositories, null, 'getPHID'); + + foreach ($phids as $phid) { + $handle = new PhabricatorObjectHandle(); + $handle->setPHID($phid); + $handle->setType($type); + if (empty($repositories[$phid])) { + $handle->setName('Unknown Repository'); + } else { + $repository = $repositories[$phid]; + $handle->setName($repository->getCallsign()); + $handle->setURI('/diffusion/'.$repository->getCallsign().'/'); + } + $handles[$phid] = $handle; + } + break; default: $loader = null; if (isset($external_loaders[$type])) { diff --git a/src/view/layout/panel/AphrontPanelView.php b/src/view/layout/panel/AphrontPanelView.php index 026c7f7094..9a3f83d064 100644 --- a/src/view/layout/panel/AphrontPanelView.php +++ b/src/view/layout/panel/AphrontPanelView.php @@ -22,20 +22,25 @@ final class AphrontPanelView extends AphrontView { const WIDTH_FORM = 'form'; const WIDTH_WIDE = 'wide'; - private $createButton; + private $buttons = array(); private $header; private $width; public function setCreateButton($create_button, $href) { + $this->addButton( + phutil_render_tag( + 'a', + array( + 'href' => $href, + 'class' => 'button green', + ), + $create_button)); - $this->createButton = phutil_render_tag( - 'a', - array( - 'href' => $href, - 'class' => 'create-button button green', - ), - $create_button); + return $this; + } + public function addButton($button) { + $this->buttons[] = $button; return $this; } @@ -56,10 +61,12 @@ final class AphrontPanelView extends AphrontView { $header = null; } - if ($this->createButton !== null) { - $button = $this->createButton; - } else { - $button = null; + $buttons = null; + if ($this->buttons) { + $buttons = + '
'. + implode(" ", $this->buttons). + '
'; } $table = $this->renderChildren(); @@ -73,7 +80,7 @@ final class AphrontPanelView extends AphrontView { return '
'. - $button. + $buttons. $header. $table. '
'; diff --git a/webroot/rsrc/css/aphront/list-filter-view.css b/webroot/rsrc/css/aphront/list-filter-view.css index f05836d929..09b59baabf 100644 --- a/webroot/rsrc/css/aphront/list-filter-view.css +++ b/webroot/rsrc/css/aphront/list-filter-view.css @@ -6,6 +6,7 @@ background: #f6f6f6; border-bottom: 1px solid #bbbbbb; width: 100%; + margin-bottom: 2em; } .aphront-list-filter-view-buttons { @@ -24,7 +25,7 @@ .aphront-list-filter-view-controls .aphront-form-view { border-width: 0; - padding: 0 0 6px; + padding: 12px 0 6px; } .aphront-list-filter-view-controls .aphront-form-view .aphront-form-label { diff --git a/webroot/rsrc/css/aphront/panel-view.css b/webroot/rsrc/css/aphront/panel-view.css index 1aaed03fa4..889510d502 100644 --- a/webroot/rsrc/css/aphront/panel-view.css +++ b/webroot/rsrc/css/aphront/panel-view.css @@ -17,7 +17,7 @@ padding: 2px 0 8px; } -.aphront-panel-view a.create-button { +.aphront-panel-view-buttons { float: right; }