mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-19 03:50:54 +01:00
Merge pull request #40 from cadamo/master
Add subprojects to a main project.
This commit is contained in:
commit
c12c42083c
10 changed files with 207 additions and 11 deletions
11
resources/sql/patches/064.subporjects.sql
Normal file
11
resources/sql/patches/064.subporjects.sql
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
ALTER TABLE phabricator_project.project
|
||||||
|
ADD subprojectPHIDs longblob NOT NULL;
|
||||||
|
UPDATE phabricator_project.project
|
||||||
|
SET subprojectPHIDs = '[]';
|
||||||
|
|
||||||
|
CREATE TABLE phabricator_project.project_subproject (
|
||||||
|
projectPHID varchar(64) BINARY NOT NULL,
|
||||||
|
subprojectPHID varchar(64) BINARY NOT NULL,
|
||||||
|
PRIMARY KEY (subprojectPHID, projectPHID),
|
||||||
|
UNIQUE KEY (projectPHID, subprojectPHID)
|
||||||
|
);
|
|
@ -43,8 +43,6 @@ class PhabricatorProjectListController
|
||||||
$handles = id(new PhabricatorObjectHandleData($author_phids))
|
$handles = id(new PhabricatorObjectHandleData($author_phids))
|
||||||
->loadHandles();
|
->loadHandles();
|
||||||
|
|
||||||
$project_phids = mpull($projects, 'getPHID');
|
|
||||||
|
|
||||||
$query = id(new ManiphestTaskQuery())
|
$query = id(new ManiphestTaskQuery())
|
||||||
->withProjects($project_phids)
|
->withProjects($project_phids)
|
||||||
->withAnyProject(true)
|
->withAnyProject(true)
|
||||||
|
|
|
@ -58,12 +58,12 @@ class PhabricatorProjectProfileController
|
||||||
'statistics' => 'Statistics',
|
'statistics' => 'Statistics',
|
||||||
'<hr />', */
|
'<hr />', */
|
||||||
'<h2>Information</h2>',
|
'<h2>Information</h2>',
|
||||||
'edit' => 'Edit Profile',
|
'edit' => 'Edit Project',
|
||||||
'affiliation' => 'Edit Affiliation',
|
'affiliation' => 'Edit Affiliation',
|
||||||
);
|
);
|
||||||
|
|
||||||
if (empty($pages[$this->page])) {
|
if (empty($pages[$this->page])) {
|
||||||
$this->page = 'action'; // key($pages);
|
$this->page = 'action';
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($this->page) {
|
switch ($this->page) {
|
||||||
|
@ -106,6 +106,9 @@ class PhabricatorProjectProfileController
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// Helper functions
|
||||||
|
|
||||||
private function renderBasicInformation($project, $profile) {
|
private function renderBasicInformation($project, $profile) {
|
||||||
$blurb = nonempty(
|
$blurb = nonempty(
|
||||||
$profile->getBlurb(),
|
$profile->getBlurb(),
|
||||||
|
@ -118,7 +121,10 @@ class PhabricatorProjectProfileController
|
||||||
|
|
||||||
$phids = array_merge(
|
$phids = array_merge(
|
||||||
array($project->getAuthorPHID()),
|
array($project->getAuthorPHID()),
|
||||||
mpull($affiliations, 'getUserPHID'));
|
$project->getSubprojectPHIDs(),
|
||||||
|
mpull($affiliations, 'getUserPHID')
|
||||||
|
);
|
||||||
|
$phids = array_unique($phids);
|
||||||
$handles = id(new PhabricatorObjectHandleData($phids))
|
$handles = id(new PhabricatorObjectHandleData($phids))
|
||||||
->loadHandles();
|
->loadHandles();
|
||||||
|
|
||||||
|
@ -134,12 +140,24 @@ class PhabricatorProjectProfileController
|
||||||
|
|
||||||
$affiliated[] = '<li>'.$user.' — '.$role.$status.'</li>';
|
$affiliated[] = '<li>'.$user.' — '.$role.$status.'</li>';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($affiliated) {
|
if ($affiliated) {
|
||||||
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
|
$affiliated = '<ul>'.implode("\n", $affiliated).'</ul>';
|
||||||
} else {
|
} else {
|
||||||
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
|
$affiliated = '<p><em>No one is affiliated with this project.</em></p>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($project->getSubprojectPHIDs()) {
|
||||||
|
$table = $this->renderSubprojectTable(
|
||||||
|
$handles,
|
||||||
|
$project->getSubprojectPHIDs());
|
||||||
|
$subproject_list = $table->render();
|
||||||
|
} else {
|
||||||
|
$subproject_list =
|
||||||
|
'<p><em>There are no projects attached for such specie.</em></p>';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
$timestamp = phabricator_format_timestamp($project->getDateCreated());
|
$timestamp = phabricator_format_timestamp($project->getDateCreated());
|
||||||
$status = PhabricatorProjectStatus::getNameForStatus(
|
$status = PhabricatorProjectStatus::getNameForStatus(
|
||||||
$project->getStatus());
|
$project->getStatus());
|
||||||
|
@ -174,12 +192,19 @@ class PhabricatorProjectProfileController
|
||||||
</div>';
|
</div>';
|
||||||
|
|
||||||
$content .=
|
$content .=
|
||||||
'<div class="phabricator-profile-info-group">
|
'<div class="phabricator-profile-info-group">'.
|
||||||
<h1 class="phabricator-profile-info-header">Resources</h1>
|
'<h1 class="phabricator-profile-info-header">Resources</h1>'.
|
||||||
<div class="phabricator-profile-info-pane">'.
|
'<div class="phabricator-profile-info-pane">'.
|
||||||
$affiliated.
|
$affiliated.
|
||||||
'</div>
|
'</div>'.
|
||||||
</div>';
|
'</div>';
|
||||||
|
|
||||||
|
$content .= '<div class="phabricator-profile-info-group">'.
|
||||||
|
'<h1 class="phabricator-profile-info-header">Subprojects</h1>'.
|
||||||
|
'<div class="phabricator-profile-info-pane">'.
|
||||||
|
$subproject_list.
|
||||||
|
'</div>'.
|
||||||
|
'</div>';
|
||||||
|
|
||||||
$query = id(new ManiphestTaskQuery())
|
$query = id(new ManiphestTaskQuery())
|
||||||
->withProjects(array($project->getPHID()))
|
->withProjects(array($project->getPHID()))
|
||||||
|
@ -234,4 +259,39 @@ class PhabricatorProjectProfileController
|
||||||
|
|
||||||
return $content;
|
return $content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function renderSubprojectTable(
|
||||||
|
PhabricatorObjectHandleData $handles,
|
||||||
|
$subprojects_phids) {
|
||||||
|
|
||||||
|
$rows = array();
|
||||||
|
foreach ($subprojects_phids as $subproject_phid) {
|
||||||
|
$phid = $handles[$subproject_phid]->getPHID();
|
||||||
|
|
||||||
|
$rows[] = array(
|
||||||
|
phutil_escape_html($handles[$phid]->getFullName()),
|
||||||
|
phutil_render_tag(
|
||||||
|
'a',
|
||||||
|
array(
|
||||||
|
'class' => 'small grey button',
|
||||||
|
'href' => $handles[$phid]->getURI(),
|
||||||
|
),
|
||||||
|
'View Project Profile'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = new AphrontTableView($rows);
|
||||||
|
$table->setHeaders(
|
||||||
|
array(
|
||||||
|
'Name',
|
||||||
|
'',
|
||||||
|
));
|
||||||
|
$table->setColumnClasses(
|
||||||
|
array(
|
||||||
|
'pri',
|
||||||
|
'action right',
|
||||||
|
));
|
||||||
|
|
||||||
|
return $table;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||||
|
phutil_require_module('phabricator', 'view/control/table');
|
||||||
phutil_require_module('phabricator', 'view/layout/profile');
|
phutil_require_module('phabricator', 'view/layout/profile');
|
||||||
phutil_require_module('phabricator', 'view/utils');
|
phutil_require_module('phabricator', 'view/utils');
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,15 @@ class PhabricatorProjectProfileEditController
|
||||||
$profile = new PhabricatorProjectProfile();
|
$profile = new PhabricatorProjectProfile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($project->getSubprojectPHIDs()) {
|
||||||
|
$phids = $project->getSubprojectPHIDs();
|
||||||
|
$handles = id(new PhabricatorObjectHandleData($phids))
|
||||||
|
->loadHandles();
|
||||||
|
$subprojects = mpull($handles, 'getFullName', 'getPHID');
|
||||||
|
} else {
|
||||||
|
$subprojects = array();
|
||||||
|
}
|
||||||
|
|
||||||
$options = PhabricatorProjectStatus::getStatusMap();
|
$options = PhabricatorProjectStatus::getStatusMap();
|
||||||
|
|
||||||
$e_name = true;
|
$e_name = true;
|
||||||
|
@ -45,6 +54,7 @@ class PhabricatorProjectProfileEditController
|
||||||
if ($request->isFormPost()) {
|
if ($request->isFormPost()) {
|
||||||
$project->setName($request->getStr('name'));
|
$project->setName($request->getStr('name'));
|
||||||
$project->setStatus($request->getStr('status'));
|
$project->setStatus($request->getStr('status'));
|
||||||
|
$project->setSubprojectPHIDs($request->getArr('set_subprojects'));
|
||||||
$profile->setBlurb($request->getStr('blurb'));
|
$profile->setBlurb($request->getStr('blurb'));
|
||||||
|
|
||||||
if (!strlen($project->getName())) {
|
if (!strlen($project->getName())) {
|
||||||
|
@ -121,6 +131,12 @@ class PhabricatorProjectProfileEditController
|
||||||
->setLabel('Blurb')
|
->setLabel('Blurb')
|
||||||
->setName('blurb')
|
->setName('blurb')
|
||||||
->setValue($profile->getBlurb()))
|
->setValue($profile->getBlurb()))
|
||||||
|
->appendChild(
|
||||||
|
id(new AphrontFormTokenizerControl())
|
||||||
|
->setDatasource('/typeahead/common/projects/')
|
||||||
|
->setLabel('Subprojects')
|
||||||
|
->setName('set_subprojects')
|
||||||
|
->setValue($subprojects))
|
||||||
->appendChild(
|
->appendChild(
|
||||||
id(new AphrontFormFileControl())
|
id(new AphrontFormFileControl())
|
||||||
->setLabel('Change Image')
|
->setLabel('Change Image')
|
||||||
|
@ -132,7 +148,7 @@ class PhabricatorProjectProfileEditController
|
||||||
|
|
||||||
$panel = new AphrontPanelView();
|
$panel = new AphrontPanelView();
|
||||||
$panel->setHeader($header_name);
|
$panel->setHeader($header_name);
|
||||||
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
$panel->setWidth(AphrontPanelView::WIDTH_WIDE);
|
||||||
$panel->appendChild($form);
|
$panel->appendChild($form);
|
||||||
|
|
||||||
return $this->buildStandardPageResponse(
|
return $this->buildStandardPageResponse(
|
||||||
|
|
|
@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'aphront/response/404');
|
||||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||||
phutil_require_module('phabricator', 'applications/files/storage/file');
|
phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||||
phutil_require_module('phabricator', 'applications/files/transform');
|
phutil_require_module('phabricator', 'applications/files/transform');
|
||||||
|
phutil_require_module('phabricator', 'applications/phid/handle/data');
|
||||||
phutil_require_module('phabricator', 'applications/project/constants/status');
|
phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||||
|
@ -20,6 +21,7 @@ phutil_require_module('phabricator', 'view/form/control/select');
|
||||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||||
phutil_require_module('phabricator', 'view/form/control/text');
|
phutil_require_module('phabricator', 'view/form/control/text');
|
||||||
phutil_require_module('phabricator', 'view/form/control/textarea');
|
phutil_require_module('phabricator', 'view/form/control/textarea');
|
||||||
|
phutil_require_module('phabricator', 'view/form/control/tokenizer');
|
||||||
phutil_require_module('phabricator', 'view/form/error');
|
phutil_require_module('phabricator', 'view/form/error');
|
||||||
phutil_require_module('phabricator', 'view/layout/panel');
|
phutil_require_module('phabricator', 'view/layout/panel');
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,16 @@ class PhabricatorProject extends PhabricatorProjectDAO {
|
||||||
protected $phid;
|
protected $phid;
|
||||||
protected $status = PhabricatorProjectStatus::UNKNOWN;
|
protected $status = PhabricatorProjectStatus::UNKNOWN;
|
||||||
protected $authorPHID;
|
protected $authorPHID;
|
||||||
|
protected $subprojectPHIDs = array();
|
||||||
|
|
||||||
|
private $subprojectsNeedUpdate;
|
||||||
|
|
||||||
public function getConfiguration() {
|
public function getConfiguration() {
|
||||||
return array(
|
return array(
|
||||||
self::CONFIG_AUX_PHID => true,
|
self::CONFIG_AUX_PHID => true,
|
||||||
|
self::CONFIG_SERIALIZATION => array(
|
||||||
|
'subprojectPHIDs' => self::SERIALIZATION_JSON,
|
||||||
|
),
|
||||||
) + parent::getConfiguration();
|
) + parent::getConfiguration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +40,12 @@ class PhabricatorProject extends PhabricatorProjectDAO {
|
||||||
PhabricatorPHIDConstants::PHID_TYPE_PROJ);
|
PhabricatorPHIDConstants::PHID_TYPE_PROJ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setSubprojectPHIDs(array $phids) {
|
||||||
|
$this->subprojectPHIDs = $phids;
|
||||||
|
$this->subprojectsNeedUpdate = true;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
public function loadProfile() {
|
public function loadProfile() {
|
||||||
$profile = id(new PhabricatorProjectProfile())->loadOneWhere(
|
$profile = id(new PhabricatorProjectProfile())->loadOneWhere(
|
||||||
'projectPHID = %s',
|
'projectPHID = %s',
|
||||||
|
@ -46,4 +58,18 @@ class PhabricatorProject extends PhabricatorProjectDAO {
|
||||||
array($this->getPHID()));
|
array($this->getPHID()));
|
||||||
return $affils[$this->getPHID()];
|
return $affils[$this->getPHID()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function save() {
|
||||||
|
$result = parent::save();
|
||||||
|
|
||||||
|
if ($this->subprojectsNeedUpdate) {
|
||||||
|
// If we've changed the project PHIDs for this task, update the link
|
||||||
|
// table.
|
||||||
|
PhabricatorProjectSubproject::updateProjectSubproject($this);
|
||||||
|
$this->subprojectsNeedUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/base');
|
phutil_require_module('phabricator', 'applications/project/storage/base');
|
||||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||||
|
phutil_require_module('phabricator', 'applications/project/storage/subproject');
|
||||||
|
|
||||||
phutil_require_module('phutil', 'utils');
|
phutil_require_module('phutil', 'utils');
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright 2011 Facebook, Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a DAO for the Task -> Project table, which denormalizes the
|
||||||
|
* relationship between tasks and projects into a link table so it can be
|
||||||
|
* efficiently queried. This table is not authoritative; the projectPHIDs field
|
||||||
|
* of ManiphestTask is. The rows in this table are regenerated when transactions
|
||||||
|
* are applied to tasks which affected their associated projects.
|
||||||
|
*
|
||||||
|
* @group maniphest
|
||||||
|
*/
|
||||||
|
final class PhabricatorProjectSubproject extends PhabricatorProjectDAO {
|
||||||
|
|
||||||
|
protected $projectPHID;
|
||||||
|
protected $subprojectPHID;
|
||||||
|
|
||||||
|
public function getConfiguration() {
|
||||||
|
return array(
|
||||||
|
self::CONFIG_IDS => self::IDS_MANUAL,
|
||||||
|
self::CONFIG_TIMESTAMPS => false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function updateProjectSubproject(PhabricatorProject $project) {
|
||||||
|
$dao = new PhabricatorProjectSubproject();
|
||||||
|
$conn = $dao->establishConnection('w');
|
||||||
|
|
||||||
|
$sql = array();
|
||||||
|
foreach ($project->getSubprojectPHIDs() as $subproject_phid) {
|
||||||
|
$sql[] = qsprintf(
|
||||||
|
$conn,
|
||||||
|
'(%s, %s)',
|
||||||
|
$project->getPHID(),
|
||||||
|
$subproject_phid);
|
||||||
|
}
|
||||||
|
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'DELETE FROM %T WHERE projectPHID = %s',
|
||||||
|
$dao->getTableName(),
|
||||||
|
$project->getPHID());
|
||||||
|
if ($sql) {
|
||||||
|
queryfx(
|
||||||
|
$conn,
|
||||||
|
'INSERT INTO %T (projectPHID, subprojectPHID) VALUES %Q',
|
||||||
|
$dao->getTableName(),
|
||||||
|
implode(', ', $sql));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
src/applications/project/storage/subproject/__init__.php
Normal file
14
src/applications/project/storage/subproject/__init__.php
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This file is automatically generated. Lint this module to rebuild it.
|
||||||
|
* @generated
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_module('phabricator', 'applications/project/storage/base');
|
||||||
|
phutil_require_module('phabricator', 'storage/qsprintf');
|
||||||
|
phutil_require_module('phabricator', 'storage/queryfx');
|
||||||
|
|
||||||
|
|
||||||
|
phutil_require_source('PhabricatorProjectSubproject.php');
|
Loading…
Reference in a new issue