Add transaction-oriented editing to projects
Summary: - Make some editing operations transaction-oriented, like Maniphest. (This seems to be a good model, particularly for extensibility.) I'll move the rest of the editing operations to transactions in future diffs. - Make transaction-oriented operations publish feed stories. Test Plan: - Created a new project. - Edited an existing project. - Created a new project via quick create flow from Maniphest. - Verified feed stories publish correctly. Reviewers: btrahan, jungejason Reviewed By: btrahan CC: aran, epriestley Maniphest Tasks: T681 Differential Revision: https://secure.phabricator.com/D1477
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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.
|
||||
*/
|
||||
|
||||
abstract class PhabricatorProjectConstants {
|
||||
|
||||
}
|
||||
10
src/applications/project/constants/base/__init__.php
Normal file
10
src/applications/project/constants/base/__init__.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorProjectConstants.php');
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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.
|
||||
*/
|
||||
|
||||
final class PhabricatorProjectTransactionType
|
||||
extends PhabricatorProjectConstants {
|
||||
|
||||
const TYPE_NAME = 'name';
|
||||
const TYPE_MEMBERS = 'members';
|
||||
|
||||
}
|
||||
12
src/applications/project/constants/transaction/__init__.php
Normal file
12
src/applications/project/constants/transaction/__init__.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/project/constants/base');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorProjectTransactionType.php');
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -34,10 +34,16 @@ class PhabricatorProjectCreateController
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
try {
|
||||
$xactions = array();
|
||||
$xaction = new PhabricatorProjectTransaction();
|
||||
$xaction->setTransactionType(
|
||||
PhabricatorProjectTransactionType::TYPE_NAME);
|
||||
$xaction->setNewValue($request->getStr('name'));
|
||||
$xactions[] = $xaction;
|
||||
|
||||
$editor = new PhabricatorProjectEditor($project);
|
||||
$editor->setUser($user);
|
||||
$editor->setName($request->getStr('name'));
|
||||
$editor->save();
|
||||
$editor->applyTransactions($xactions);
|
||||
} catch (PhabricatorProjectNameCollisionException $ex) {
|
||||
$e_name = 'Not Unique';
|
||||
$errors[] = $ex->getMessage();
|
||||
|
||||
@@ -10,11 +10,13 @@ phutil_require_module('phabricator', 'aphront/response/ajax');
|
||||
phutil_require_module('phabricator', 'aphront/response/dialog');
|
||||
phutil_require_module('phabricator', 'aphront/response/redirect');
|
||||
phutil_require_module('phabricator', 'applications/project/constants/status');
|
||||
phutil_require_module('phabricator', 'applications/project/constants/transaction');
|
||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/project/editor/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/transaction');
|
||||
phutil_require_module('phabricator', 'view/dialog');
|
||||
phutil_require_module('phabricator', 'view/form/base');
|
||||
phutil_require_module('phabricator', 'view/form/control/submit');
|
||||
|
||||
@@ -58,10 +58,16 @@ class PhabricatorProjectProfileEditController
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
try {
|
||||
$xactions = array();
|
||||
$xaction = new PhabricatorProjectTransaction();
|
||||
$xaction->setTransactionType(
|
||||
PhabricatorProjectTransactionType::TYPE_NAME);
|
||||
$xaction->setNewValue($request->getStr('name'));
|
||||
$xactions[] = $xaction;
|
||||
|
||||
$editor = new PhabricatorProjectEditor($project);
|
||||
$editor->setUser($user);
|
||||
$editor->setName($request->getStr('name'));
|
||||
$editor->save();
|
||||
$editor->applyTransactions($xactions);
|
||||
} catch (PhabricatorProjectNameCollisionException $ex) {
|
||||
$e_name = 'Not Unique';
|
||||
$errors[] = $ex->getMessage();
|
||||
|
||||
@@ -12,11 +12,13 @@ phutil_require_module('phabricator', 'applications/files/storage/file');
|
||||
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/transaction');
|
||||
phutil_require_module('phabricator', 'applications/project/controller/base');
|
||||
phutil_require_module('phabricator', 'applications/project/editor/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/profile');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/transaction');
|
||||
phutil_require_module('phabricator', 'infrastructure/celerity/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/api');
|
||||
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2011 Facebook, Inc.
|
||||
* Copyright 2012 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,45 +20,64 @@ final class PhabricatorProjectEditor {
|
||||
|
||||
private $project;
|
||||
private $user;
|
||||
|
||||
private $projectName;
|
||||
|
||||
private $addAffiliations;
|
||||
private $remAffiliations;
|
||||
|
||||
public function __construct(PhabricatorProject $project) {
|
||||
$this->project = $project;
|
||||
}
|
||||
|
||||
public function setName($name) {
|
||||
$this->projectName = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(PhabricatorUser $user) {
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function save() {
|
||||
public function applyTransactions(array $transactions) {
|
||||
if (!$this->user) {
|
||||
throw new Exception('Call setUser() before save()!');
|
||||
}
|
||||
$user = $this->user;
|
||||
|
||||
$project = $this->project;
|
||||
|
||||
$is_new = !$project->getID();
|
||||
|
||||
if ($is_new) {
|
||||
$project->setAuthorPHID($this->user->getPHID());
|
||||
$project->setAuthorPHID($user->getPHID());
|
||||
}
|
||||
|
||||
if (($this->projectName !== null) &&
|
||||
($this->projectName !== $project->getName())) {
|
||||
$project->setName($this->projectName);
|
||||
$project->setPhrictionSlug($this->projectName);
|
||||
$this->validateName($project);
|
||||
foreach ($transactions as $xaction) {
|
||||
$type = $xaction->getTransactionType();
|
||||
|
||||
$this->setTransactionOldValue($project, $xaction);
|
||||
$this->applyTransactionEffect($project, $xaction);
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
$project->save();
|
||||
|
||||
foreach ($transactions as $xaction) {
|
||||
$xaction->setAuthorPHID($user->getPHID());
|
||||
$xaction->setProjectID($project->getID());
|
||||
$xaction->save();
|
||||
}
|
||||
|
||||
foreach ($this->remAffiliations as $affil) {
|
||||
$affil->delete();
|
||||
}
|
||||
|
||||
foreach ($this->addAffiliations as $affil) {
|
||||
$affil->setProjectPHID($project->getPHID());
|
||||
$affil->save();
|
||||
}
|
||||
|
||||
foreach ($transactions as $xaction) {
|
||||
$this->publishTransactionStory($project, $xaction);
|
||||
}
|
||||
|
||||
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||
// We already validated the slug, but might race. Try again to see if
|
||||
// that's the issue. If it is, we'll throw a more specific exception. If
|
||||
@@ -100,4 +119,97 @@ final class PhabricatorProjectEditor {
|
||||
}
|
||||
}
|
||||
|
||||
private function setTransactionOldValue(
|
||||
PhabricatorProject $project,
|
||||
PhabricatorProjectTransaction $xaction) {
|
||||
|
||||
$type = $xaction->getTransactionType();
|
||||
switch ($type) {
|
||||
case PhabricatorProjectTransactionType::TYPE_NAME:
|
||||
$xaction->setOldValue($project->getName());
|
||||
break;
|
||||
case PhabricatorProjectTransactionType::TYPE_MEMBERS:
|
||||
$affils = $project->loadAffiliations();
|
||||
$project->attachAffiliations($affils);
|
||||
|
||||
$old_value = mpull($affils, 'getUserPHID');
|
||||
$old_value = array_values($old_value);
|
||||
$xaction->setOldValue($affils);
|
||||
|
||||
$new_value = $xaction->getNewValue();
|
||||
$new_value = array_filter($new_value);
|
||||
$new_value = array_unique($new_value);
|
||||
$new_value = array_values($new_value);
|
||||
$xaction->setNewValue($new_value);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown transaction type '{$type}'!");
|
||||
}
|
||||
}
|
||||
|
||||
private function applyTransactionEffect(
|
||||
PhabricatorProject $project,
|
||||
PhabricatorProjectTransaction $xaction) {
|
||||
|
||||
$type = $xaction->getTransactionType();
|
||||
switch ($type) {
|
||||
case PhabricatorProjectTransactionType::TYPE_NAME:
|
||||
$project->setName($xaction->getNewValue());
|
||||
$project->setPhrictionSlug($xaction->getNewValue());
|
||||
$this->validateName($project);
|
||||
break;
|
||||
case PhabricatorProjectTransactionType::TYPE_MEMBERS:
|
||||
$old = array_fill_keys($xaction->getOldValue(), true);
|
||||
$new = array_fill_keys($xaction->getNewValue(), true);
|
||||
|
||||
$add = array();
|
||||
$rem = array();
|
||||
|
||||
foreach ($project->getAffiliations() as $affil) {
|
||||
if (empty($new[$affil->getUserPHID()])) {
|
||||
$rem[] = $affil;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($new as $phid => $ignored) {
|
||||
if (empty($old[$phid])) {
|
||||
$affil = new PhabricatorProjectAffiliation();
|
||||
$affil->setUserPHID($phid);
|
||||
$add[] = $affil;
|
||||
}
|
||||
}
|
||||
|
||||
$this->addAffiliations = $add;
|
||||
$this->remAffiliations = $rem;
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unknown transaction type '{$type}'!");
|
||||
}
|
||||
}
|
||||
|
||||
private function publishTransactionStory(
|
||||
PhabricatorProject $project,
|
||||
PhabricatorProjectTransaction $xaction) {
|
||||
|
||||
$related_phids = array(
|
||||
$project->getPHID(),
|
||||
$xaction->getAuthorPHID(),
|
||||
);
|
||||
|
||||
id(new PhabricatorFeedStoryPublisher())
|
||||
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_PROJECT)
|
||||
->setStoryData(
|
||||
array(
|
||||
'projectPHID' => $project->getPHID(),
|
||||
'transactionID' => $xaction->getID(),
|
||||
'type' => $xaction->getTransactionType(),
|
||||
'old' => $xaction->getOldValue(),
|
||||
'new' => $xaction->getNewValue(),
|
||||
))
|
||||
->setStoryTime(time())
|
||||
->setStoryAuthorPHID($xaction->getAuthorPHID())
|
||||
->setRelatedPHIDs($related_phids)
|
||||
->publish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,11 @@
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/feed/constants/story');
|
||||
phutil_require_module('phabricator', 'applications/feed/publisher');
|
||||
phutil_require_module('phabricator', 'applications/project/constants/transaction');
|
||||
phutil_require_module('phabricator', 'applications/project/exception/namecollison');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/affiliation');
|
||||
phutil_require_module('phabricator', 'applications/project/storage/project');
|
||||
|
||||
phutil_require_module('phutil', 'utils');
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Copyright 2012 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @group project
|
||||
*/
|
||||
class PhabricatorProjectTransaction extends PhabricatorProjectDAO {
|
||||
|
||||
protected $projectID;
|
||||
protected $authorPHID;
|
||||
protected $transactionType;
|
||||
protected $oldValue;
|
||||
protected $newValue;
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'oldValue' => self::SERIALIZATION_JSON,
|
||||
'newValue' => self::SERIALIZATION_JSON,
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
}
|
||||
12
src/applications/project/storage/transaction/__init__.php
Normal file
12
src/applications/project/storage/transaction/__init__.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is automatically generated. Lint this module to rebuild it.
|
||||
* @generated
|
||||
*/
|
||||
|
||||
|
||||
|
||||
phutil_require_module('phabricator', 'applications/project/storage/base');
|
||||
|
||||
|
||||
phutil_require_source('PhabricatorProjectTransaction.php');
|
||||
Reference in New Issue
Block a user