Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F1843268
Custom Actions "framework"
No One
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Authored By
avivey
Sep 21 2016, 7:41 PM
2016-09-21 19:41:02 (UTC+0)
Size
8 KB
Referenced Files
None
Subscribers
None
Custom Actions "framework"
View Options
<?php
/**
* This is just a thin layer on top of the regular Form patern, which mostly
* saves a bit of boilerplate.
* It's not particularly good, but it's better then replicating the code for
* each action.
* It also assumes that each Action is exactly "Apply one Transaction", which is
* an over-simplification.
*/
abstract
class
MagicReleaseCustomAction
extends
Phobject
{
private
$viewer
;
abstract
protected
function
getFormTitle
(
PhabricatorReleaseRelease
$release
);
abstract
protected
function
getFormPremble
(
PhabricatorReleaseRelease
$release
);
abstract
public
function
isAllowedForTemplateKey
(
$template_key
);
public
function
getSubmitText
()
{
return
pht
(
'Submit'
);
}
abstract
public
function
getActionItemText
();
public
function
getActionItemIcon
()
{
return
null
;
}
public
function
initialValidationsForRelease
(
PhabricatorReleaseRelease
$release
)
{
$errors
=
array
();
$template_key
=
$release
->
getReleaseTemplateKey
();
if
(!
$this
->
isAllowedForTemplateKey
(
$template_key
))
{
$errors
[]
=
'This Release type does not support this action'
;
return
$errors
;
}
if
(
$this
->
requiresBranches
())
{
$branch_name
=
$release
->
getBranchNameForAllRepos
();
if
(!
strlen
(
$branch_name
))
{
$errors
[]
=
'This revision doesn
\'
t have a consistent branches, so we can
\'
t '
.
'update it.'
;
}
}
if
(
$this
->
requiresNoBranches
())
{
$refs
=
$release
->
getCurrentRefs
();
foreach
(
$refs
as
$ref
)
{
if
(
$release
->
isBranch
(
$ref
))
{
$errors
[]
=
'This release has branches, so this action does not apply.'
;
break
;
}
}
}
return
$errors
;
}
protected
function
requiresBranches
()
{
return
false
;
}
protected
function
requiresNoBranches
()
{
return
false
;
}
public
function
needsEditPermission
()
{
return
true
;
}
public
function
assertPolicy
(
$release
)
{
// maybe `get required capabilities()?
if
(
$this
->
needsEditPermission
())
{
// it would be nicer to fold this into $errors[] instead of exception.
PhabricatorPolicyFilter
::
requireCapability
(
$this
->
getViewer
(),
$release
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
}
}
/**
* Get initial values to put in the form. Returns array of fields.
*/
abstract
public
function
getDefaultFieldValues
(
PhabricatorReleaseRelease
$release
);
/**
* Build the form. $fields is the output of getDefaultFieldValues() or
* handleForm().
*/
abstract
public
function
buildForm
(
PhabricatorReleaseRelease
$release
,
array
$fields
);
/**
* Receives the dialog Post; Updates $fields, and any validation errors.
* Doesn't actually take any action.
* return array($fields, $errors)
*/
abstract
public
function
handleFormPost
(
PhabricatorReleaseRelease
$release
,
AphrontRequest
$request
,
array
$fields
);
/**
* The transaction will actually do the action.
* This method is only called if both handleFormPost() and
* initialValidationsForRelease() return no errors.
*
* return TransactionType (string) that will run the action.
*/
abstract
public
function
getTransactionType
(
PhabricatorReleaseRelease
$release
,
array
$fields
);
/**
* Produce the transaction's initial value; This will be updated again
* by the transaction itself.
*/
abstract
public
function
generateTransactionValue
(
PhabricatorReleaseRelease
$release
,
array
$fields
);
/**
* Where to go after applying the transaction.
*/
public
function
getResultURI
(
PhabricatorReleaseRelease
$release
,
array
$fields
,
PhabricatorReleaseReleaseTransaction
$xaction
)
{
return
$release
->
getURI
();
}
final
public
function
setViewer
(
PhabricatorUser
$viewer
)
{
$this
->
viewer
=
$viewer
;
}
final
public
function
getViewer
()
{
return
$this
->
viewer
;
}
}
final
class
MagicReleaseCustomActionController
extends
PhabricatorController
{
public
function
handleRequest
(
AphrontRequest
$request
)
{
$viewer
=
$this
->
getViewer
();
$release_id
=
$request
->
getURIData
(
'release_id'
);
$release
=
id
(
new
PhabricatorReleaseReleaseQuery
())
->
setViewer
(
$viewer
)
->
withIDs
(
array
(
$release_id
))
->
executeOne
();
if
(!
$release
)
{
return
new
Aphront404Response
();
}
$action_class
=
$request
->
getURIData
(
'action'
);
$custom_action
=
is_subclass_of
(
$action_class
,
'MagicReleaseCustomAction'
);
if
(
$custom_action
)
{
$action
=
newv
(
$action_class
,
array
());
$action
->
setViewer
(
$viewer
);
}
else
{
throw
new
Exception
(
pht
(
"Action type must be a valid class name and must subclass "
.
"%s. '%s' is not a subclass of %s"
,
'MagicReleaseCustomAction'
,
$this
->
strategyClass
,
'MagicReleaseCustomAction'
));
}
$action
->
assertPolicy
(
$release
);
$fields
=
$action
->
getDefaultFieldValues
(
$release
);
$errors
=
$action
->
initialValidationsForRelease
(
$release
);
if
(
$request
->
isDialogFormPost
())
{
list
(
$fields
,
$errors2
)
=
$action
->
handleFormPost
(
$release
,
$request
,
$fields
);
$errors
=
array_merge
(
$errors
,
$errors2
);
if
(!
$errors
)
{
$xaction_type
=
$action
->
getTransactionType
(
$release
,
$fields
);
$value
=
$action
->
generateTransactionValue
(
$release
,
$fields
);
$xaction
=
id
(
new
PhabricatorReleaseReleaseTransaction
())
->
setTransactionType
(
$xaction_type
)
->
setNewValue
(
$value
);
$editor
=
id
(
new
PhabricatorReleaseReleaseEditor
())
->
setActor
(
$viewer
)
->
setContentSourceFromRequest
(
$request
)
->
setContinueOnNoEffect
(
true
);
try
{
$editor
->
applyTransactions
(
$release
,
array
(
$xaction
));
$uri
=
$action
->
getResultURI
(
$release
,
$fields
,
$xaction
);
return
id
(
new
AphrontRedirectResponse
())->
setURI
(
$uri
);
}
catch
(
PhabricatorApplicationTransactionValidationException
$ex
)
{
$errors
[]
=
'Failed to initiate action! '
.
$ex
->
getMessage
();
}
}
}
$preamble
=
$action
->
getFormPremble
(
$release
);
$preamble
=
new
PHUIRemarkupView
(
$viewer
,
$preamble
);
$form
=
$action
->
buildForm
(
$release
,
$fields
);
$form
->
setViewer
(
$viewer
);
return
$this
->
newDialog
()
->
setSubmitURI
(
$request
->
getRequestURI
())
->
setTitle
(
$action
->
getFormTitle
(
$release
))
->
appendChild
(
$preamble
)
->
setErrors
(
$errors
)
->
appendForm
(
$form
)
->
addSubmitButton
(
$action
->
getSubmitText
())
->
addCancelButton
(
'#'
);
}
}
final
class
MagicRenderEventListener
extends
PhabricatorEventListener
{
public
function
handleEvent
(
PhutilEvent
$event
)
{
switch
(
$event
->
getType
())
{
case
PhabricatorEventType
::
TYPE_UI_DIDRENDERACTIONS
:
if
(
$object
instanceof
PhabricatorReleaseRelease
)
{
$this
->
addReleaseActions
(
$event
);
}
break
;
}
}
private
function
addReleaseActions
(
PhutilEvent
$event
)
{
$release
=
$event
->
getValue
(
'object'
);
$release_id
=
$release
->
getID
();
$template_key
=
$release
->
getReleaseTemplateKey
();
$can_edit
=
PhabricatorPolicyFilter
::
hasCapability
(
$event
->
getUser
(),
$release
,
PhabricatorPolicyCapability
::
CAN_EDIT
);
$actions
=
array
();
$custom_actions
=
id
(
new
PhutilClassMapQuery
())
->
setAncestorClass
(
'MagicReleaseCustomAction'
)
->
execute
();
foreach
(
$custom_actions
as
$custom_action
)
{
if
(
$custom_action
->
isAllowedForTemplateKey
(
$template_key
))
{
$action_class
=
get_class
(
$custom_action
);
$action
=
id
(
new
PhabricatorActionView
())
->
setName
(
$custom_action
->
getActionItemText
())
->
setWorkflow
(
true
)
->
setIcon
(
$custom_action
->
getActionItemIcon
())
->
setHref
(
"/magic/release/{$release_id}/action/{$action_class}/"
);
if
(
$custom_action
->
needsEditPermission
())
{
$action
->
setDisabled
(!
$can_edit
);
}
$actions
[]
=
$action
;
}
}
$actions
[]
=
id
(
new
PhabricatorActionView
())
->
setName
(
'Compare to Another Release'
)
->
setWorkflow
(
true
)
->
setIcon
(
'fa-search'
)
->
setHref
(
"/magic/release/{$release_id}/compare/"
);
$this
->
addActionMenuItems
(
$event
,
$actions
);
}
}
File Metadata
Details
Attached
Mime Type
text/plain; charset=utf-8
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
289512
Default Alt Text
Custom Actions "framework" (8 KB)
Attached To
Mode
P2009 Custom Actions "framework"
Attached
Detach File
Event Timeline
Log In to Comment