1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-01-10 14:51:06 +01:00

(stable) Promote 2018 Week 8

This commit is contained in:
epriestley 2018-02-24 08:15:48 -08:00
commit 3f2419eebb
62 changed files with 2186 additions and 566 deletions

View file

@ -9,8 +9,8 @@ return array(
'names' => array(
'conpherence.pkg.css' => 'e68cf1fa',
'conpherence.pkg.js' => '15191c65',
'core.pkg.css' => 'e4f098a5',
'core.pkg.js' => 'bd19de1c',
'core.pkg.css' => '2fa91e14',
'core.pkg.js' => 'dc13d4b7',
'darkconsole.pkg.js' => '1f9a31bc',
'differential.pkg.css' => '113e692c',
'differential.pkg.js' => 'f6d809c0',
@ -31,7 +31,7 @@ return array(
'rsrc/css/aphront/multi-column.css' => '84cc6640',
'rsrc/css/aphront/notification.css' => '457861ec',
'rsrc/css/aphront/panel-view.css' => '8427b78d',
'rsrc/css/aphront/phabricator-nav-view.css' => '028126f6',
'rsrc/css/aphront/phabricator-nav-view.css' => 'a9e3e6d5',
'rsrc/css/aphront/table-view.css' => '8c9bbafe',
'rsrc/css/aphront/tokenizer.css' => '15d5ff71',
'rsrc/css/aphront/tooltip.css' => '173b9431',
@ -498,7 +498,7 @@ return array(
'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => '77c1f0b0',
'rsrc/js/core/behavior-oncopy.js' => '2926fff2',
'rsrc/js/core/behavior-phabricator-nav.js' => '81144dfa',
'rsrc/js/core/behavior-phabricator-nav.js' => '836f966d',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => 'acd29eee',
'rsrc/js/core/behavior-read-only-warning.js' => 'ba158207',
'rsrc/js/core/behavior-refresh-csrf.js' => 'ab2f381b',
@ -657,7 +657,7 @@ return array(
'javelin-behavior-phabricator-keyboard-pager' => 'a8da01f0',
'javelin-behavior-phabricator-keyboard-shortcuts' => '01fca1f0',
'javelin-behavior-phabricator-line-linker' => '1499a8cb',
'javelin-behavior-phabricator-nav' => '81144dfa',
'javelin-behavior-phabricator-nav' => '836f966d',
'javelin-behavior-phabricator-notification-example' => '8ce821c5',
'javelin-behavior-phabricator-object-selector' => '77c1f0b0',
'javelin-behavior-phabricator-oncopy' => '2926fff2',
@ -789,7 +789,7 @@ return array(
'phabricator-keyboard-shortcut' => '1ae869f2',
'phabricator-keyboard-shortcut-manager' => 'c19dd9b9',
'phabricator-main-menu-view' => '1802a242',
'phabricator-nav-view-css' => '028126f6',
'phabricator-nav-view-css' => 'a9e3e6d5',
'phabricator-notification' => '008faf9c',
'phabricator-notification-css' => '457861ec',
'phabricator-notification-menu-css' => '10685bd4',
@ -1563,7 +1563,11 @@ return array(
'phuix-icon-view',
'phabricator-prefab',
),
'81144dfa' => array(
'834a1173' => array(
'javelin-behavior',
'javelin-scrollbar',
),
'836f966d' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
@ -1573,10 +1577,6 @@ return array(
'javelin-request',
'javelin-util',
),
'834a1173' => array(
'javelin-behavior',
'javelin-scrollbar',
),
'8499b6ab' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -0,0 +1,5 @@
CREATE TABLE {$NAMESPACE}_fact.fact_keydimension (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
factKey VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT},
UNIQUE KEY `key_factkey` (factKey)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,5 @@
CREATE TABLE {$NAMESPACE}_fact.fact_objectdimension (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectPHID VARBINARY(64) NOT NULL,
UNIQUE KEY `key_object` (objectPHID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,8 @@
CREATE TABLE {$NAMESPACE}_fact.fact_intdatapoint (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
keyID INT UNSIGNED NOT NULL,
objectID INT UNSIGNED NOT NULL,
dimensionID INT UNSIGNED,
value BIGINT SIGNED NOT NULL,
epoch INT UNSIGNED NOT NULL
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -2588,6 +2588,7 @@ phutil_register_library_map(array(
'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php',
'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php',
'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php',
'PhabricatorCountFact' => 'applications/fact/fact/PhabricatorCountFact.php',
'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php',
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
@ -2616,6 +2617,9 @@ phutil_register_library_map(array(
'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php',
'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php',
'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php',
'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php',
'PhabricatorCustomFieldApplicationSearchDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchDatasource.php',
'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php',
'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php',
'PhabricatorCustomFieldConfigOptionType' => 'infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php',
'PhabricatorCustomFieldDataNotAvailableException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldDataNotAvailableException.php',
@ -2907,27 +2911,29 @@ phutil_register_library_map(array(
'PhabricatorExternalAccountsSettingsPanel' => 'applications/settings/panel/PhabricatorExternalAccountsSettingsPanel.php',
'PhabricatorExtraConfigSetupCheck' => 'applications/config/check/PhabricatorExtraConfigSetupCheck.php',
'PhabricatorFacebookAuthProvider' => 'applications/auth/provider/PhabricatorFacebookAuthProvider.php',
'PhabricatorFact' => 'applications/fact/fact/PhabricatorFact.php',
'PhabricatorFactAggregate' => 'applications/fact/storage/PhabricatorFactAggregate.php',
'PhabricatorFactApplication' => 'applications/fact/application/PhabricatorFactApplication.php',
'PhabricatorFactChartController' => 'applications/fact/controller/PhabricatorFactChartController.php',
'PhabricatorFactController' => 'applications/fact/controller/PhabricatorFactController.php',
'PhabricatorFactCountEngine' => 'applications/fact/engine/PhabricatorFactCountEngine.php',
'PhabricatorFactCursor' => 'applications/fact/storage/PhabricatorFactCursor.php',
'PhabricatorFactDAO' => 'applications/fact/storage/PhabricatorFactDAO.php',
'PhabricatorFactDaemon' => 'applications/fact/daemon/PhabricatorFactDaemon.php',
'PhabricatorFactDatapointQuery' => 'applications/fact/query/PhabricatorFactDatapointQuery.php',
'PhabricatorFactDimension' => 'applications/fact/storage/PhabricatorFactDimension.php',
'PhabricatorFactEngine' => 'applications/fact/engine/PhabricatorFactEngine.php',
'PhabricatorFactEngineTestCase' => 'applications/fact/engine/__tests__/PhabricatorFactEngineTestCase.php',
'PhabricatorFactHomeController' => 'applications/fact/controller/PhabricatorFactHomeController.php',
'PhabricatorFactLastUpdatedEngine' => 'applications/fact/engine/PhabricatorFactLastUpdatedEngine.php',
'PhabricatorFactIntDatapoint' => 'applications/fact/storage/PhabricatorFactIntDatapoint.php',
'PhabricatorFactKeyDimension' => 'applications/fact/storage/PhabricatorFactKeyDimension.php',
'PhabricatorFactManagementAnalyzeWorkflow' => 'applications/fact/management/PhabricatorFactManagementAnalyzeWorkflow.php',
'PhabricatorFactManagementCursorsWorkflow' => 'applications/fact/management/PhabricatorFactManagementCursorsWorkflow.php',
'PhabricatorFactManagementDestroyWorkflow' => 'applications/fact/management/PhabricatorFactManagementDestroyWorkflow.php',
'PhabricatorFactManagementListWorkflow' => 'applications/fact/management/PhabricatorFactManagementListWorkflow.php',
'PhabricatorFactManagementStatusWorkflow' => 'applications/fact/management/PhabricatorFactManagementStatusWorkflow.php',
'PhabricatorFactManagementWorkflow' => 'applications/fact/management/PhabricatorFactManagementWorkflow.php',
'PhabricatorFactObjectController' => 'applications/fact/controller/PhabricatorFactObjectController.php',
'PhabricatorFactObjectDimension' => 'applications/fact/storage/PhabricatorFactObjectDimension.php',
'PhabricatorFactRaw' => 'applications/fact/storage/PhabricatorFactRaw.php',
'PhabricatorFactSimpleSpec' => 'applications/fact/spec/PhabricatorFactSimpleSpec.php',
'PhabricatorFactSpec' => 'applications/fact/spec/PhabricatorFactSpec.php',
'PhabricatorFactUpdateIterator' => 'applications/fact/extract/PhabricatorFactUpdateIterator.php',
'PhabricatorFavoritesApplication' => 'applications/favorites/application/PhabricatorFavoritesApplication.php',
'PhabricatorFavoritesController' => 'applications/favorites/controller/PhabricatorFavoritesController.php',
@ -3040,6 +3046,7 @@ phutil_register_library_map(array(
'PhabricatorFilesOnDiskBuiltinFile' => 'applications/files/builtin/PhabricatorFilesOnDiskBuiltinFile.php',
'PhabricatorFilesOutboundRequestAction' => 'applications/files/action/PhabricatorFilesOutboundRequestAction.php',
'PhabricatorFiletreeVisibleSetting' => 'applications/settings/setting/PhabricatorFiletreeVisibleSetting.php',
'PhabricatorFiletreeWidthSetting' => 'applications/settings/setting/PhabricatorFiletreeWidthSetting.php',
'PhabricatorFlag' => 'applications/flag/storage/PhabricatorFlag.php',
'PhabricatorFlagAddFlagHeraldAction' => 'applications/flag/herald/PhabricatorFlagAddFlagHeraldAction.php',
'PhabricatorFlagColor' => 'applications/flag/constants/PhabricatorFlagColor.php',
@ -3260,6 +3267,7 @@ phutil_register_library_map(array(
'PhabricatorManagementWorkflow' => 'infrastructure/management/PhabricatorManagementWorkflow.php',
'PhabricatorManiphestApplication' => 'applications/maniphest/application/PhabricatorManiphestApplication.php',
'PhabricatorManiphestConfigOptions' => 'applications/maniphest/config/PhabricatorManiphestConfigOptions.php',
'PhabricatorManiphestTaskFactEngine' => 'applications/fact/engine/PhabricatorManiphestTaskFactEngine.php',
'PhabricatorManiphestTaskTestDataGenerator' => 'applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php',
'PhabricatorManualActivitySetupCheck' => 'applications/config/check/PhabricatorManualActivitySetupCheck.php',
'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php',
@ -3716,6 +3724,7 @@ phutil_register_library_map(array(
'PhabricatorPirateEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorPirateEnglishTranslation.php',
'PhabricatorPlatformSite' => 'aphront/site/PhabricatorPlatformSite.php',
'PhabricatorPointsEditField' => 'applications/transactions/editfield/PhabricatorPointsEditField.php',
'PhabricatorPointsFact' => 'applications/fact/fact/PhabricatorPointsFact.php',
'PhabricatorPolicies' => 'applications/policy/constants/PhabricatorPolicies.php',
'PhabricatorPolicy' => 'applications/policy/storage/PhabricatorPolicy.php',
'PhabricatorPolicyApplication' => 'applications/policy/application/PhabricatorPolicyApplication.php',
@ -4357,6 +4366,7 @@ phutil_register_library_map(array(
'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php',
'PhabricatorTokensToken' => 'applications/tokens/storage/PhabricatorTokensToken.php',
'PhabricatorTransactionChange' => 'applications/transactions/data/PhabricatorTransactionChange.php',
'PhabricatorTransactionFactEngine' => 'applications/fact/engine/PhabricatorTransactionFactEngine.php',
'PhabricatorTransactionRemarkupChange' => 'applications/transactions/data/PhabricatorTransactionRemarkupChange.php',
'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php',
'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php',
@ -4381,6 +4391,7 @@ phutil_register_library_map(array(
'PhabricatorTypeaheadInvalidTokenException' => 'applications/typeahead/exception/PhabricatorTypeaheadInvalidTokenException.php',
'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php',
'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php',
'PhabricatorTypeaheadProxyDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php',
'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php',
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php',
'PhabricatorTypeaheadTestNumbersDatasource' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php',
@ -8076,6 +8087,7 @@ phutil_register_library_map(array(
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
'PhabricatorCountFact' => 'PhabricatorFact',
'PhabricatorCountdown' => array(
'PhabricatorCountdownDAO',
'PhabricatorPolicyInterface',
@ -8115,6 +8127,9 @@ phutil_register_library_map(array(
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
'PhabricatorCustomField' => 'Phobject',
'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorCustomFieldApplicationSearchDatasource' => 'PhabricatorTypeaheadProxyDatasource',
'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorCustomFieldAttachment' => 'Phobject',
'PhabricatorCustomFieldConfigOptionType' => 'PhabricatorConfigOptionType',
'PhabricatorCustomFieldDataNotAvailableException' => 'Exception',
@ -8433,27 +8448,29 @@ phutil_register_library_map(array(
'PhabricatorExternalAccountsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorExtraConfigSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorFacebookAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorFact' => 'Phobject',
'PhabricatorFactAggregate' => 'PhabricatorFactDAO',
'PhabricatorFactApplication' => 'PhabricatorApplication',
'PhabricatorFactChartController' => 'PhabricatorFactController',
'PhabricatorFactController' => 'PhabricatorController',
'PhabricatorFactCountEngine' => 'PhabricatorFactEngine',
'PhabricatorFactCursor' => 'PhabricatorFactDAO',
'PhabricatorFactDAO' => 'PhabricatorLiskDAO',
'PhabricatorFactDaemon' => 'PhabricatorDaemon',
'PhabricatorFactDatapointQuery' => 'Phobject',
'PhabricatorFactDimension' => 'PhabricatorFactDAO',
'PhabricatorFactEngine' => 'Phobject',
'PhabricatorFactEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorFactHomeController' => 'PhabricatorFactController',
'PhabricatorFactLastUpdatedEngine' => 'PhabricatorFactEngine',
'PhabricatorFactIntDatapoint' => 'PhabricatorFactDAO',
'PhabricatorFactKeyDimension' => 'PhabricatorFactDimension',
'PhabricatorFactManagementAnalyzeWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementCursorsWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementDestroyWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementListWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementStatusWorkflow' => 'PhabricatorFactManagementWorkflow',
'PhabricatorFactManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorFactObjectController' => 'PhabricatorFactController',
'PhabricatorFactObjectDimension' => 'PhabricatorFactDimension',
'PhabricatorFactRaw' => 'PhabricatorFactDAO',
'PhabricatorFactSimpleSpec' => 'PhabricatorFactSpec',
'PhabricatorFactSpec' => 'Phobject',
'PhabricatorFactUpdateIterator' => 'PhutilBufferedIterator',
'PhabricatorFavoritesApplication' => 'PhabricatorApplication',
'PhabricatorFavoritesController' => 'PhabricatorController',
@ -8597,6 +8614,7 @@ phutil_register_library_map(array(
'PhabricatorFilesOnDiskBuiltinFile' => 'PhabricatorFilesBuiltinFile',
'PhabricatorFilesOutboundRequestAction' => 'PhabricatorSystemAction',
'PhabricatorFiletreeVisibleSetting' => 'PhabricatorInternalSetting',
'PhabricatorFiletreeWidthSetting' => 'PhabricatorInternalSetting',
'PhabricatorFlag' => array(
'PhabricatorFlagDAO',
'PhabricatorPolicyInterface',
@ -8824,6 +8842,7 @@ phutil_register_library_map(array(
'PhabricatorManagementWorkflow' => 'PhutilArgumentWorkflow',
'PhabricatorManiphestApplication' => 'PhabricatorApplication',
'PhabricatorManiphestConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorManiphestTaskFactEngine' => 'PhabricatorTransactionFactEngine',
'PhabricatorManiphestTaskTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorManualActivitySetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorMarkupCache' => 'PhabricatorCacheDAO',
@ -9373,6 +9392,7 @@ phutil_register_library_map(array(
'PhabricatorPirateEnglishTranslation' => 'PhutilTranslation',
'PhabricatorPlatformSite' => 'PhabricatorSite',
'PhabricatorPointsEditField' => 'PhabricatorEditField',
'PhabricatorPointsFact' => 'PhabricatorFact',
'PhabricatorPolicies' => 'PhabricatorPolicyConstants',
'PhabricatorPolicy' => array(
'PhabricatorPolicyDAO',
@ -10146,6 +10166,7 @@ phutil_register_library_map(array(
'PhabricatorConduitResultInterface',
),
'PhabricatorTransactionChange' => 'Phobject',
'PhabricatorTransactionFactEngine' => 'PhabricatorFactEngine',
'PhabricatorTransactionRemarkupChange' => 'PhabricatorTransactionChange',
'PhabricatorTransactions' => 'Phobject',
'PhabricatorTransactionsApplication' => 'PhabricatorApplication',
@ -10170,6 +10191,7 @@ phutil_register_library_map(array(
'PhabricatorTypeaheadInvalidTokenException' => 'Exception',
'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorTypeaheadProxyDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorTypeaheadResult' => 'Phobject',
'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'PhabricatorTypeaheadTestNumbersDatasource' => 'PhabricatorTypeaheadDatasource',

View file

@ -77,6 +77,7 @@ final class PhabricatorAuthAccountView extends AphrontView {
array(
'href' => $account_uri,
'target' => '_blank',
'rel' => 'noreferrer',
),
$account_uri);
}

View file

@ -45,6 +45,7 @@ final class PhabricatorCalendarICSURIImportEngine
array(
'href' => $uri,
'target' => '_blank',
'rel' => 'noreferrer',
),
$uri);
}

View file

@ -35,12 +35,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
);
}
public function getFactObjectsForAnalysis() {
return array(
new DifferentialRevision(),
);
}
public function getTitleGlyph() {
return "\xE2\x9A\x99";
}

View file

@ -473,10 +473,14 @@ final class DifferentialRevisionViewController extends DifferentialController {
$collapsed_key = PhabricatorFiletreeVisibleSetting::SETTINGKEY;
$collapsed_value = $viewer->getUserSetting($collapsed_key);
$width_key = PhabricatorFiletreeWidthSetting::SETTINGKEY;
$width_value = $viewer->getUserSetting($width_key);
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setTitle($monogram)
->setBaseURI(new PhutilURI($revision->getURI()))
->setCollapsed((bool)$collapsed_value)
->setWidth((int)$width_value)
->build($changesets);
}

View file

@ -6,6 +6,7 @@ final class DifferentialChangesetFileTreeSideNavBuilder extends Phobject {
private $baseURI;
private $anchorName;
private $collapsed = false;
private $width;
public function setAnchorName($anchor_name) {
$this->anchorName = $anchor_name;
@ -36,13 +37,19 @@ final class DifferentialChangesetFileTreeSideNavBuilder extends Phobject {
return $this;
}
public function setWidth($width) {
$this->width = $width;
return $this;
}
public function build(array $changesets) {
assert_instances_of($changesets, 'DifferentialChangeset');
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI($this->getBaseURI());
$nav->setFlexible(true);
$nav->setCollapsed($this->collapsed);
$nav = id(new AphrontSideNavFilterView())
->setBaseURI($this->getBaseURI())
->setFlexible(true)
->setCollapsed($this->collapsed)
->setWidth($this->width);
$anchor = $this->getAnchorName();

View file

@ -39,12 +39,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
);
}
public function getFactObjectsForAnalysis() {
return array(
new PhabricatorRepositoryCommit(),
);
}
public function getRemarkupRules() {
return array(
new DiffusionCommitRemarkupRule(),

View file

@ -415,17 +415,21 @@ final class DiffusionCommitController extends DiffusionController {
PhabricatorShowFiletreeSetting::SETTINGKEY,
PhabricatorShowFiletreeSetting::VALUE_ENABLE_FILETREE);
$pref_collapse = PhabricatorFiletreeVisibleSetting::SETTINGKEY;
$collapsed = $viewer->getUserSetting($pref_collapse);
$nav = null;
if ($show_changesets && $filetree_on) {
$pref_collapse = PhabricatorFiletreeVisibleSetting::SETTINGKEY;
$collapsed = $viewer->getUserSetting($pref_collapse);
$pref_width = PhabricatorFiletreeWidthSetting::SETTINGKEY;
$width = $viewer->getUserSetting($pref_width);
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setTitle($commit->getDisplayName())
->setBaseURI(new PhutilURI($commit->getURI()))
->build($changesets)
->setCrumbs($crumbs)
->setCollapsed((bool)$collapsed);
->setCollapsed((bool)$collapsed)
->setWidth((int)$width);
}
$view = id(new PHUITwoColumnView())

View file

@ -31,6 +31,7 @@ final class PhabricatorFactApplication extends PhabricatorApplication {
'/fact/' => array(
'' => 'PhabricatorFactHomeController',
'chart/' => 'PhabricatorFactChartController',
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
),
);
}

View file

@ -5,27 +5,35 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$table = new PhabricatorFactRaw();
$series = $request->getStr('y1');
$facts = PhabricatorFact::getAllFacts();
$fact = idx($facts, $series);
if (!$fact) {
return new Aphront404Response();
}
$key_id = id(new PhabricatorFactKeyDimension())
->newDimensionID($fact->getKey());
if (!$key_id) {
return new Aphront404Response();
}
$table = $fact->newDatapoint();
$conn_r = $table->establishConnection('r');
$table_name = $table->getTableName();
$series = $request->getStr('y1');
$specs = PhabricatorFactSpec::newSpecsForFactTypes(
PhabricatorFactEngine::loadAllEngines(),
array($series));
$spec = idx($specs, $series);
$data = queryfx_all(
$conn_r,
'SELECT valueX, epoch FROM %T WHERE factType = %s ORDER BY epoch ASC',
'SELECT value, epoch FROM %T WHERE keyID = %d ORDER BY epoch ASC',
$table_name,
$series);
$key_id);
$points = array();
$sum = 0;
foreach ($data as $key => $row) {
$sum += (int)$row['valueX'];
$sum += (int)$row['value'];
$points[(int)$row['epoch']] = $sum;
}
@ -71,7 +79,7 @@ final class PhabricatorFactChartController extends PhabricatorFactController {
));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Count of %s', $spec->getName()))
->setHeaderText(pht('Count of %s', $fact->getName()))
->appendChild($chart);
$crumbs = $this->buildApplicationCrumbs();

View file

@ -15,45 +15,6 @@ final class PhabricatorFactHomeController extends PhabricatorFactController {
return id(new AphrontRedirectResponse())->setURI($uri);
}
$types = array(
'+N:*',
'+N:DREV',
'updated',
);
$engines = PhabricatorFactEngine::loadAllEngines();
$specs = PhabricatorFactSpec::newSpecsForFactTypes($engines, $types);
$facts = id(new PhabricatorFactAggregate())->loadAllWhere(
'factType IN (%Ls)',
$types);
$rows = array();
foreach ($facts as $fact) {
$spec = $specs[$fact->getFactType()];
$name = $spec->getName();
$value = $spec->formatValueForDisplay($viewer, $fact->getValueX());
$rows[] = array($name, $value);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
pht('Fact'),
pht('Value'),
));
$table->setColumnClasses(
array(
'wide',
'n',
));
$panel = new PHUIObjectBoxView();
$panel->setHeaderText(pht('Facts'));
$panel->setTable($table);
$chart_form = $this->buildChartForm();
$crumbs = $this->buildApplicationCrumbs();
@ -64,46 +25,18 @@ final class PhabricatorFactHomeController extends PhabricatorFactController {
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(array(
$chart_form,
$panel,
));
->appendChild(
array(
$chart_form,
));
}
private function buildChartForm() {
$request = $this->getRequest();
$viewer = $request->getUser();
$table = new PhabricatorFactRaw();
$conn_r = $table->establishConnection('r');
$table_name = $table->getTableName();
$facts = queryfx_all(
$conn_r,
'SELECT DISTINCT factType from %T',
$table_name);
$specs = PhabricatorFactSpec::newSpecsForFactTypes(
PhabricatorFactEngine::loadAllEngines(),
ipull($facts, 'factType'));
$options = array();
foreach ($specs as $spec) {
if ($spec->getUnit() == PhabricatorFactSpec::UNIT_COUNT) {
$options[$spec->getType()] = $spec->getName();
}
}
if (!$options) {
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NODATA)
->setTitle(pht('No Chartable Facts'))
->appendChild(phutil_tag(
'p',
array(),
pht('There are no facts that can be plotted yet.')));
}
$specs = PhabricatorFact::getAllFacts();
$options = mpull($specs, 'getName', 'getKey');
$form = id(new AphrontFormView())
->setUser($viewer)

View file

@ -0,0 +1,323 @@
<?php
final class PhabricatorFactObjectController
extends PhabricatorFactController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$phid = $request->getURIData('phid');
$object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames(array($phid))
->executeOne();
if (!$object) {
return new Aphront404Response();
}
$engines = PhabricatorFactEngine::loadAllEngines();
foreach ($engines as $key => $engine) {
$engine = id(clone $engine)
->setViewer($viewer);
$engines[$key] = $engine;
if (!$engine->supportsDatapointsForObject($object)) {
unset($engines[$key]);
}
}
if (!$engines) {
return $this->newDialog()
->setTitle(pht('No Engines'))
->appendParagraph(
pht(
'No fact engines support generating facts for this object.'))
->addCancelButton($this->getApplicationURI());
}
$key_dimension = new PhabricatorFactKeyDimension();
$object_phid = $object->getPHID();
$facts = array();
$generated_datapoints = array();
$timings = array();
foreach ($engines as $key => $engine) {
$engine_facts = $engine->newFacts();
$engine_facts = mpull($engine_facts, null, 'getKey');
$facts[$key] = $engine_facts;
$t_start = microtime(true);
$generated_datapoints[$key] = $engine->newDatapointsForObject($object);
$t_end = microtime(true);
$timings[$key] = ($t_end - $t_start);
}
$object_id = id(new PhabricatorFactObjectDimension())
->newDimensionID($object_phid, true);
$stored_datapoints = id(new PhabricatorFactDatapointQuery())
->withFacts(array_mergev($facts))
->withObjectPHIDs(array($object_phid))
->needVectors(true)
->execute();
$stored_groups = array();
foreach ($stored_datapoints as $stored_datapoint) {
$stored_groups[$stored_datapoint['key']][] = $stored_datapoint;
}
$stored_map = array();
foreach ($engines as $key => $engine) {
$stored_map[$key] = array();
foreach ($facts[$key] as $fact) {
$fact_datapoints = idx($stored_groups, $fact->getKey(), array());
$fact_datapoints = igroup($fact_datapoints, 'vector');
$stored_map[$key] += $fact_datapoints;
}
}
$handle_phids = array();
$handle_phids[] = $object->getPHID();
foreach ($generated_datapoints as $key => $datapoint_set) {
foreach ($datapoint_set as $datapoint) {
$dimension_phid = $datapoint->getDimensionPHID();
if ($dimension_phid !== null) {
$handle_phids[$dimension_phid] = $dimension_phid;
}
}
}
foreach ($stored_map as $key => $stored_datapoints) {
foreach ($stored_datapoints as $vector_key => $datapoints) {
foreach ($datapoints as $datapoint) {
$dimension_phid = $datapoint['dimensionPHID'];
if ($dimension_phid !== null) {
$handle_phids[$dimension_phid] = $dimension_phid;
}
}
}
}
$handles = $viewer->loadHandles($handle_phids);
$dimension_map = id(new PhabricatorFactObjectDimension())
->newDimensionMap($handle_phids, true);
$content = array();
$object_list = id(new PHUIPropertyListView())
->setViewer($viewer)
->addProperty(
pht('Object'),
$handles[$object->getPHID()]->renderLink());
$total_cost = array_sum($timings);
$total_cost = pht('%sms', new PhutilNumber((int)(1000 * $total_cost)));
$object_list->addProperty(pht('Total Cost'), $total_cost);
$object_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Fact Extraction Report'))
->addPropertyList($object_list);
$content[] = $object_box;
$icon_fact = id(new PHUIIconView())
->setIcon('fa-line-chart green')
->setTooltip(pht('Consistent Fact'));
$icon_nodata = id(new PHUIIconView())
->setIcon('fa-question-circle-o violet')
->setTooltip(pht('No Stored Datapoints'));
$icon_new = id(new PHUIIconView())
->setIcon('fa-plus red')
->setTooltip(pht('Not Stored'));
$icon_surplus = id(new PHUIIconView())
->setIcon('fa-minus red')
->setTooltip(pht('Not Generated'));
foreach ($engines as $key => $engine) {
$rows = array();
foreach ($generated_datapoints[$key] as $datapoint) {
$dimension_phid = $datapoint->getDimensionPHID();
if ($dimension_phid !== null) {
$dimension = $handles[$datapoint->getDimensionPHID()]->renderLink();
} else {
$dimension = null;
}
$fact_key = $datapoint->getKey();
$fact = idx($facts[$key], $fact_key, null);
if ($fact) {
$fact_label = $fact->getName();
} else {
$fact_label = $fact_key;
}
$vector_key = $datapoint->newDatapointVector();
if (isset($stored_map[$key][$vector_key])) {
unset($stored_map[$key][$vector_key]);
$icon = $icon_fact;
} else {
$icon = $icon_new;
}
$rows[] = array(
$icon,
$fact_label,
$dimension,
$datapoint->getValue(),
phabricator_datetime($datapoint->getEpoch(), $viewer),
);
}
foreach ($stored_map[$key] as $vector_key => $datapoints) {
foreach ($datapoints as $datapoint) {
$dimension_phid = $datapoint['dimensionPHID'];
if ($dimension_phid !== null) {
$dimension = $handles[$dimension_phid]->renderLink();
} else {
$dimension = null;
}
$fact_key = $datapoint['key'];
$fact = idx($facts[$key], $fact_key, null);
if ($fact) {
$fact_label = $fact->getName();
} else {
$fact_label = $fact_key;
}
$rows[] = array(
$icon_surplus,
$fact_label,
$dimension,
$datapoint['value'],
phabricator_datetime($datapoint['epoch'], $viewer),
);
}
}
foreach ($facts[$key] as $fact) {
$has_any = id(new PhabricatorFactDatapointQuery())
->withFacts(array($fact))
->setLimit(1)
->execute();
if ($has_any) {
continue;
}
if (!$has_any) {
$rows[] = array(
$icon_nodata,
$fact->getName(),
null,
null,
null,
);
}
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
null,
pht('Fact'),
pht('Dimension'),
pht('Value'),
pht('Date'),
))
->setColumnClasses(
array(
'',
'',
'',
'n wide right',
'right',
));
$extraction_cost = $timings[$key];
$extraction_cost = pht(
'%sms',
new PhutilNumber((int)(1000 * $extraction_cost)));
$header = pht(
'%s (%s)',
get_class($engine),
$extraction_cost);
$box = id(new PHUIObjectBoxView())
->setHeaderText($header)
->setTable($table);
$content[] = $box;
if ($engine instanceof PhabricatorTransactionFactEngine) {
$groups = $engine->newTransactionGroupsForObject($object);
$groups = array_values($groups);
$xaction_phids = array();
foreach ($groups as $group_key => $xactions) {
foreach ($xactions as $xaction) {
$xaction_phids[] = $xaction->getAuthorPHID();
}
}
$xaction_handles = $viewer->loadHandles($xaction_phids);
$rows = array();
foreach ($groups as $group_key => $xactions) {
foreach ($xactions as $xaction) {
$rows[] = array(
$group_key,
$xaction->getTransactionType(),
$xaction_handles[$xaction->getAuthorPHID()]->renderLink(),
phabricator_datetime($xaction->getDateCreated(), $viewer),
);
}
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Group'),
pht('Type'),
pht('Author'),
pht('Date'),
))
->setColumnClasses(
array(
null,
'pri',
'wide',
'right',
));
$header = pht(
'%s (Transactions)',
get_class($engine));
$xaction_box = id(new PHUIObjectBoxView())
->setHeaderText($header)
->setTable($table);
$content[] = $xaction_box;
}
}
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Chart'));
$title = pht('Chart');
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($content);
}
}

View file

@ -4,8 +4,6 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
private $engines;
const RAW_FACT_BUFFER_LIMIT = 128;
protected function run() {
$this->setEngines(PhabricatorFactEngine::loadAllEngines());
while (!$this->shouldExit()) {
@ -15,7 +13,6 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
foreach ($iterators as $iterator_name => $iterator) {
$this->processIteratorWithCursor($iterator_name, $iterator);
}
$this->processAggregates();
$this->log(pht('Zzz...'));
$this->sleep(60 * 5);
@ -65,6 +62,11 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
public function setEngines(array $engines) {
assert_instances_of($engines, 'PhabricatorFactEngine');
$viewer = PhabricatorUser::getOmnipotentUser();
foreach ($engines as $engine) {
$engine->setViewer($viewer);
}
$this->engines = $engines;
return $this;
}
@ -72,59 +74,49 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
public function processIterator($iterator) {
$result = null;
$raw_facts = array();
$datapoints = array();
$count = 0;
foreach ($iterator as $key => $object) {
$phid = $object->getPHID();
$this->log(pht('Processing %s...', $phid));
$raw_facts[$phid] = $this->computeRawFacts($object);
if (count($raw_facts) > self::RAW_FACT_BUFFER_LIMIT) {
$this->updateRawFacts($raw_facts);
$raw_facts = array();
$object_datapoints = $this->newDatapoints($object);
$count += count($object_datapoints);
$datapoints[$phid] = $object_datapoints;
if ($count > 1024) {
$this->updateDatapoints($datapoints);
$datapoints = array();
$count = 0;
}
$result = $key;
}
if ($raw_facts) {
$this->updateRawFacts($raw_facts);
$raw_facts = array();
if ($count) {
$this->updateDatapoints($datapoints);
$datapoints = array();
$count = 0;
}
return $result;
}
public function processAggregates() {
$this->log(pht('Processing aggregates.'));
$facts = $this->computeAggregateFacts();
$this->updateAggregateFacts($facts);
}
private function computeAggregateFacts() {
private function newDatapoints(PhabricatorLiskDAO $object) {
$facts = array();
foreach ($this->engines as $engine) {
if (!$engine->shouldComputeAggregateFacts()) {
if (!$engine->supportsDatapointsForObject($object)) {
continue;
}
$facts[] = $engine->computeAggregateFacts();
}
return array_mergev($facts);
}
private function computeRawFacts(PhabricatorLiskDAO $object) {
$facts = array();
foreach ($this->engines as $engine) {
if (!$engine->shouldComputeRawFactsForObject($object)) {
continue;
}
$facts[] = $engine->computeRawFactsForObject($object);
$facts[] = $engine->newDatapointsForObject($object);
}
return array_mergev($facts);
}
private function updateRawFacts(array $map) {
private function updateDatapoints(array $map) {
foreach ($map as $phid => $facts) {
assert_instances_of($facts, 'PhabricatorFactRaw');
assert_instances_of($facts, 'PhabricatorFactIntDatapoint');
}
$phids = array_keys($map);
@ -132,76 +124,78 @@ final class PhabricatorFactDaemon extends PhabricatorDaemon {
return;
}
$table = new PhabricatorFactRaw();
$fact_keys = array();
$objects = array();
foreach ($map as $phid => $facts) {
foreach ($facts as $fact) {
$fact_keys[$fact->getKey()] = true;
$object_phid = $fact->getObjectPHID();
$objects[$object_phid] = $object_phid;
$dimension_phid = $fact->getDimensionPHID();
if ($dimension_phid !== null) {
$objects[$dimension_phid] = $dimension_phid;
}
}
}
$key_map = id(new PhabricatorFactKeyDimension())
->newDimensionMap(array_keys($fact_keys), true);
$object_map = id(new PhabricatorFactObjectDimension())
->newDimensionMap(array_keys($objects), true);
$table = new PhabricatorFactIntDatapoint();
$conn = $table->establishConnection('w');
$table_name = $table->getTableName();
$sql = array();
foreach ($map as $phid => $facts) {
foreach ($facts as $fact) {
$key_id = $key_map[$fact->getKey()];
$object_id = $object_map[$fact->getObjectPHID()];
$dimension_phid = $fact->getDimensionPHID();
if ($dimension_phid !== null) {
$dimension_id = $object_map[$dimension_phid];
} else {
$dimension_id = null;
}
$sql[] = qsprintf(
$conn,
'(%s, %s, %s, %d, %d, %d)',
$fact->getFactType(),
$fact->getObjectPHID(),
$fact->getObjectA(),
$fact->getValueX(),
$fact->getValueY(),
'(%d, %d, %nd, %d, %d)',
$key_id,
$object_id,
$dimension_id,
$fact->getValue(),
$fact->getEpoch());
}
}
$rebuilt_ids = array_select_keys($object_map, $phids);
$table->openTransaction();
queryfx(
$conn,
'DELETE FROM %T WHERE objectPHID IN (%Ls)',
'DELETE FROM %T WHERE objectID IN (%Ld)',
$table_name,
$phids);
$rebuilt_ids);
if ($sql) {
foreach (array_chunk($sql, 256) as $chunk) {
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
queryfx(
$conn,
'INSERT INTO %T
(factType, objectPHID, objectA, valueX, valueY, epoch)
(keyID, objectID, dimensionID, value, epoch)
VALUES %Q',
$table_name,
implode(', ', $chunk));
$chunk);
}
}
$table->saveTransaction();
}
private function updateAggregateFacts(array $facts) {
if (!$facts) {
return;
}
$table = new PhabricatorFactAggregate();
$conn = $table->establishConnection('w');
$table_name = $table->getTableName();
$sql = array();
foreach ($facts as $fact) {
$sql[] = qsprintf(
$conn,
'(%s, %s, %d)',
$fact->getFactType(),
$fact->getObjectPHID(),
$fact->getValueX());
}
foreach (array_chunk($sql, 256) as $chunk) {
queryfx(
$conn,
'INSERT INTO %T (factType, objectPHID, valueX) VALUES %Q
ON DUPLICATE KEY UPDATE valueX = VALUES(valueX)',
$table_name,
implode(', ', $chunk));
}
}
}

View file

@ -1,86 +0,0 @@
<?php
/**
* Simple fact engine which counts objects.
*/
final class PhabricatorFactCountEngine extends PhabricatorFactEngine {
public function getFactSpecs(array $fact_types) {
$results = array();
foreach ($fact_types as $type) {
if (!strncmp($type, '+N:', 3)) {
if ($type == '+N:*') {
$name = pht('Total Objects');
} else {
$name = pht('Total Objects of type %s', substr($type, 3));
}
$results[] = id(new PhabricatorFactSimpleSpec($type))
->setName($name)
->setUnit(PhabricatorFactSimpleSpec::UNIT_COUNT);
}
if (!strncmp($type, 'N:', 2)) {
if ($type == 'N:*') {
$name = pht('Objects');
} else {
$name = pht('Objects of type %s', substr($type, 2));
}
$results[] = id(new PhabricatorFactSimpleSpec($type))
->setName($name)
->setUnit(PhabricatorFactSimpleSpec::UNIT_COUNT);
}
}
return $results;
}
public function shouldComputeRawFactsForObject(PhabricatorLiskDAO $object) {
return true;
}
public function computeRawFactsForObject(PhabricatorLiskDAO $object) {
$facts = array();
$phid = $object->getPHID();
$type = phid_get_type($phid);
foreach (array('N:*', 'N:'.$type) as $fact_type) {
$facts[] = id(new PhabricatorFactRaw())
->setFactType($fact_type)
->setObjectPHID($phid)
->setValueX(1)
->setEpoch($object->getDateCreated());
}
return $facts;
}
public function shouldComputeAggregateFacts() {
return true;
}
public function computeAggregateFacts() {
$table = new PhabricatorFactRaw();
$table_name = $table->getTableName();
$conn = $table->establishConnection('r');
$counts = queryfx_all(
$conn,
'SELECT factType, SUM(valueX) N FROM %T WHERE factType LIKE %>
GROUP BY factType',
$table_name,
'N:');
$facts = array();
foreach ($counts as $count) {
$facts[] = id(new PhabricatorFactAggregate())
->setFactType('+'.$count['factType'])
->setValueX($count['N']);
}
return $facts;
}
}

View file

@ -2,30 +2,51 @@
abstract class PhabricatorFactEngine extends Phobject {
private $factMap;
private $viewer;
final public static function loadAllEngines() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->execute();
}
public function getFactSpecs(array $fact_types) {
return array();
abstract public function newFacts();
abstract public function supportsDatapointsForObject(
PhabricatorLiskDAO $object);
abstract public function newDatapointsForObject(PhabricatorLiskDAO $object);
final protected function getFact($key) {
if ($this->factMap === null) {
$facts = $this->newFacts();
$facts = mpull($facts, null, 'getKey');
$this->factMap = $facts;
}
if (!isset($this->factMap[$key])) {
throw new Exception(
pht(
'Unknown fact ("%s") for engine "%s".',
$key,
get_class($this)));
}
return $this->factMap[$key];
}
public function shouldComputeRawFactsForObject(PhabricatorLiskDAO $object) {
return false;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function computeRawFactsForObject(PhabricatorLiskDAO $object) {
return array();
}
public function getViewer() {
if (!$this->viewer) {
throw new PhutilInvalidStateException('setViewer');
}
public function shouldComputeAggregateFacts() {
return false;
}
public function computeAggregateFacts() {
return array();
return $this->viewer;
}
}

View file

@ -1,34 +0,0 @@
<?php
/**
* Engine that records the time facts were last updated.
*/
final class PhabricatorFactLastUpdatedEngine extends PhabricatorFactEngine {
public function getFactSpecs(array $fact_types) {
$results = array();
foreach ($fact_types as $type) {
if ($type == 'updated') {
$results[] = id(new PhabricatorFactSimpleSpec($type))
->setName(pht('Facts Last Updated'))
->setUnit(PhabricatorFactSimpleSpec::UNIT_EPOCH);
}
}
return $results;
}
public function shouldComputeAggregateFacts() {
return true;
}
public function computeAggregateFacts() {
$facts = array();
$facts[] = id(new PhabricatorFactAggregate())
->setFactType('updated')
->setValueX(time());
return $facts;
}
}

View file

@ -0,0 +1,411 @@
<?php
final class PhabricatorManiphestTaskFactEngine
extends PhabricatorTransactionFactEngine {
public function newFacts() {
return array(
id(new PhabricatorCountFact())
->setKey('tasks.count.create'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.create'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.status'),
id(new PhabricatorCountFact())
->setKey('tasks.count.create.project'),
id(new PhabricatorCountFact())
->setKey('tasks.count.assign.project'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.create.project'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.status.project'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.assign.project'),
id(new PhabricatorCountFact())
->setKey('tasks.count.create.owner'),
id(new PhabricatorCountFact())
->setKey('tasks.count.assign.owner'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.create.owner'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.status.owner'),
id(new PhabricatorCountFact())
->setKey('tasks.open-count.assign.owner'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.create'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.score'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.create'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.status'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.score'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.create.project'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.assign.project'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.score.project'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.create.project'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.status.project'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.score.project'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.assign.project'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.create.owner'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.assign.owner'),
id(new PhabricatorPointsFact())
->setKey('tasks.points.score.owner'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.create.owner'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.status.owner'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.score.owner'),
id(new PhabricatorPointsFact())
->setKey('tasks.open-points.assign.owner'),
);
}
public function supportsDatapointsForObject(PhabricatorLiskDAO $object) {
return ($object instanceof ManiphestTask);
}
public function newDatapointsForObject(PhabricatorLiskDAO $object) {
$xaction_groups = $this->newTransactionGroupsForObject($object);
$old_open = false;
$old_points = 0;
$old_owner = null;
$project_map = array();
$object_phid = $object->getPHID();
$is_create = true;
$specs = array();
$datapoints = array();
foreach ($xaction_groups as $xaction_group) {
$add_projects = array();
$rem_projects = array();
$new_open = $old_open;
$new_points = $old_points;
$new_owner = $old_owner;
if ($is_create) {
// Assume tasks start open.
// TODO: This might be a questionable assumption?
$new_open = true;
}
$group_epoch = last($xaction_group)->getDateCreated();
foreach ($xaction_group as $xaction) {
$old_value = $xaction->getOldValue();
$new_value = $xaction->getNewValue();
switch ($xaction->getTransactionType()) {
case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
$new_open = !ManiphestTaskStatus::isClosedStatus($new_value);
break;
case ManiphestTaskMergedIntoTransaction::TRANSACTIONTYPE:
// When a task is merged into another task, it is changed to a
// closed status without generating a separate status transaction.
$new_open = false;
break;
case ManiphestTaskPointsTransaction::TRANSACTIONTYPE:
$new_points = (int)$xaction->getNewValue();
break;
case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
$new_owner = $xaction->getNewValue();
break;
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $xaction->getMetadataValue('edge:type');
switch ($edge_type) {
case PhabricatorProjectObjectHasProjectEdgeType::EDGECONST:
$record = PhabricatorEdgeChangeRecord::newFromTransaction(
$xaction);
$add_projects += array_fuse($record->getAddedPHIDs());
$rem_projects += array_fuse($record->getRemovedPHIDs());
break;
}
break;
}
}
// If a project was both added and removed, moot it.
$mix_projects = array_intersect_key($add_projects, $rem_projects);
$add_projects = array_diff_key($add_projects, $mix_projects);
$rem_projects = array_diff_key($rem_projects, $mix_projects);
$project_sets = array(
array(
'phids' => $rem_projects,
'scale' => -1,
),
array(
'phids' => $add_projects,
'scale' => 1,
),
);
if ($is_create) {
$action = 'create';
$action_points = $new_points;
$include_open = $new_open;
} else {
$action = 'assign';
$action_points = $old_points;
$include_open = $old_open;
}
foreach ($project_sets as $project_set) {
$scale = $project_set['scale'];
foreach ($project_set['phids'] as $project_phid) {
if ($include_open) {
$specs[] = array(
"tasks.open-count.{$action}.project",
1 * $scale,
$project_phid,
);
$specs[] = array(
"tasks.open-points.{$action}.project",
$action_points * $scale,
$project_phid,
);
}
$specs[] = array(
"tasks.count.{$action}.project",
1 * $scale,
$project_phid,
);
$specs[] = array(
"tasks.points.{$action}.project",
$action_points * $scale,
$project_phid,
);
if ($scale < 0) {
unset($project_map[$project_phid]);
} else {
$project_map[$project_phid] = $project_phid;
}
}
}
if ($new_owner !== $old_owner) {
$owner_sets = array(
array(
'phid' => $old_owner,
'scale' => -1,
),
array(
'phid' => $new_owner,
'scale' => 1,
),
);
foreach ($owner_sets as $owner_set) {
$owner_phid = $owner_set['phid'];
if ($owner_phid === null) {
continue;
}
$scale = $owner_set['scale'];
if ($old_open != $new_open) {
$specs[] = array(
"tasks.open-count.{$action}.owner",
1 * $scale,
$owner_phid,
);
$specs[] = array(
"tasks.open-points.{$action}.owner",
$action_points * $scale,
$owner_phid,
);
}
$specs[] = array(
"tasks.count.{$action}.owner",
1 * $scale,
$owner_phid,
);
if ($action_points) {
$specs[] = array(
"tasks.points.{$action}.owner",
$action_points * $scale,
$owner_phid,
);
}
}
}
if ($is_create) {
$specs[] = array(
'tasks.count.create',
1,
);
$specs[] = array(
'tasks.points.create',
$new_points,
);
if ($new_open) {
$specs[] = array(
'tasks.open-count.create',
1,
);
$specs[] = array(
'tasks.open-points.create',
$new_points,
);
}
} else if ($new_open !== $old_open) {
if ($new_open) {
$scale = 1;
} else {
$scale = -1;
}
$specs[] = array(
'tasks.open-count.status',
1 * $scale,
);
$specs[] = array(
'tasks.open-points.status',
$action_points * $scale,
);
if ($new_owner !== null) {
$specs[] = array(
'tasks.open-count.status.owner',
1 * $scale,
$new_owner,
);
$specs[] = array(
'tasks.open-points.status.owner',
$action_points * $scale,
$new_owner,
);
}
foreach ($project_map as $project_phid) {
$specs[] = array(
'tasks.open-count.status.project',
1 * $scale,
$project_phid,
);
$specs[] = array(
'tasks.open-points.status.project',
$action_points * $scale,
$project_phid,
);
}
}
// The "score" facts only apply to rescoring tasks which already
// exist, so we skip them if the task is being created.
if (($new_points !== $old_points) && !$is_create) {
$delta = ($new_points - $old_points);
$specs[] = array(
'tasks.points.score',
$delta,
);
foreach ($project_map as $project_phid) {
$specs[] = array(
'tasks.points.score.project',
$delta,
$project_phid,
);
if ($old_open && $new_open) {
$specs[] = array(
'tasks.open-points.score.project',
$delta,
$project_phid,
);
}
}
if ($new_owner !== null) {
$specs[] = array(
'tasks.points.score.owner',
$delta,
$new_owner,
);
if ($old_open && $new_open) {
$specs[] = array(
'tasks.open-points.score.owner',
$delta,
$new_owner,
);
}
}
if ($old_open && $new_open) {
$specs[] = array(
'tasks.open-points.score',
$delta,
);
}
}
$old_points = $new_points;
$old_open = $new_open;
$old_owner = $new_owner;
foreach ($specs as $spec) {
$spec_key = $spec[0];
$spec_value = $spec[1];
// Don't write any facts with a value of 0. The "count" facts never
// have a value of 0, and the "points" facts aren't meaningful if
// they have a value of 0.
if ($spec_value == 0) {
continue;
}
$datapoint = $this->getFact($spec_key)
->newDatapoint();
$datapoint
->setObjectPHID($object_phid)
->setValue($spec_value)
->setEpoch($group_epoch);
if (isset($spec[2])) {
$datapoint->setDimensionPHID($spec[2]);
}
$datapoints[] = $datapoint;
}
$specs = array();
$is_create = false;
}
return $datapoints;
}
}

View file

@ -0,0 +1,84 @@
<?php
abstract class PhabricatorTransactionFactEngine
extends PhabricatorFactEngine {
public function newTransactionGroupsForObject(PhabricatorLiskDAO $object) {
$viewer = $this->getViewer();
$xaction_query = PhabricatorApplicationTransactionQuery::newQueryForObject(
$object);
$xactions = $xaction_query
->setViewer($viewer)
->withObjectPHIDs(array($object->getPHID()))
->execute();
$xactions = msortv($xactions, 'newChronologicalSortVector');
return $this->groupTransactions($xactions);
}
protected function groupTransactions(array $xactions) {
// These grouping rules are generally much looser than the display grouping
// rules. As long as the same user is editing the task and they don't leave
// it alone for a particularly long time, we'll group things together.
$breaks = array();
$touch_window = phutil_units('15 minutes in seconds');
$user_type = PhabricatorPeopleUserPHIDType::TYPECONST;
$last_actor = null;
$last_epoch = null;
foreach ($xactions as $key => $xaction) {
$this_actor = $xaction->getAuthorPHID();
if (phid_get_type($this_actor) != $user_type) {
$this_actor = null;
}
if ($this_actor && $last_actor && ($this_actor != $last_actor)) {
$breaks[$key] = true;
}
// If too much time passed between changes, group them separately.
$this_epoch = $xaction->getDateCreated();
if ($last_epoch) {
if (($this_epoch - $last_epoch) > $touch_window) {
$breaks[$key] = true;
}
}
// The clock gets reset every time the same real user touches the
// task, but does not reset if an automated actor touches things.
if (!$last_actor || ($this_actor == $last_actor)) {
$last_epoch = $this_epoch;
}
if ($this_actor && ($last_actor != $this_actor)) {
$last_actor = $this_actor;
$last_epoch = $this_epoch;
}
}
$groups = array();
$group = array();
foreach ($xactions as $key => $xaction) {
if (isset($breaks[$key])) {
if ($group) {
$groups[] = $group;
$group = array();
}
}
$group[] = $xaction;
}
if ($group) {
$groups[] = $group;
}
return $groups;
}
}

View file

@ -0,0 +1,9 @@
<?php
final class PhabricatorCountFact extends PhabricatorFact {
protected function newTemplateDatapoint() {
return new PhabricatorFactIntDatapoint();
}
}

View file

@ -0,0 +1,40 @@
<?php
abstract class PhabricatorFact extends Phobject {
private $key;
public static function getAllFacts() {
$engines = PhabricatorFactEngine::loadAllEngines();
$map = array();
foreach ($engines as $engine) {
$facts = $engine->newFacts();
$facts = mpull($facts, null, 'getKey');
$map += $facts;
}
return $map;
}
final public function setKey($key) {
$this->key = $key;
return $this;
}
final public function getKey() {
return $this->key;
}
final public function getName() {
return pht('Fact "%s"', $this->getKey());
}
final public function newDatapoint() {
return $this->newTemplateDatapoint()
->setKey($this->getKey());
}
abstract protected function newTemplateDatapoint();
}

View file

@ -0,0 +1,9 @@
<?php
final class PhabricatorPointsFact extends PhabricatorFact {
protected function newTemplateDatapoint() {
return new PhabricatorFactIntDatapoint();
}
}

View file

@ -58,10 +58,6 @@ final class PhabricatorFactManagementAnalyzeWorkflow
}
}
if (!$args->getArg('skip-aggregates')) {
$daemon->processAggregates();
}
return 0;
}

View file

@ -23,8 +23,13 @@ final class PhabricatorFactManagementDestroyWorkflow
}
$tables = array();
$tables[] = new PhabricatorFactRaw();
$tables[] = new PhabricatorFactAggregate();
$tables[] = new PhabricatorFactCursor();
$tables[] = new PhabricatorFactIntDatapoint();
$tables[] = new PhabricatorFactObjectDimension();
$tables[] = new PhabricatorFactKeyDimension();
foreach ($tables as $table) {
$conn = $table->establishConnection('w');
$name = $table->getTableName();

View file

@ -1,47 +0,0 @@
<?php
final class PhabricatorFactManagementStatusWorkflow
extends PhabricatorFactManagementWorkflow {
protected function didConstruct() {
$this
->setName('status')
->setSynopsis(pht('Show status of fact data.'))
->setArguments(array());
}
public function execute(PhutilArgumentParser $args) {
$console = PhutilConsole::getConsole();
$map = array(
'raw' => new PhabricatorFactRaw(),
'agg' => new PhabricatorFactAggregate(),
);
foreach ($map as $type => $table) {
$conn = $table->establishConnection('r');
$name = $table->getTableName();
$row = queryfx_one(
$conn,
'SELECT COUNT(*) N FROM %T',
$name);
$n = $row['N'];
switch ($type) {
case 'raw':
$desc = pht('There are %d raw fact(s) in storage.', $n);
break;
case 'agg':
$desc = pht('There are %d aggregate fact(s) in storage.', $n);
break;
}
$console->writeOut("%s\n", $desc);
}
return 0;
}
}

View file

@ -0,0 +1,181 @@
<?php
final class PhabricatorFactDatapointQuery extends Phobject {
private $facts;
private $objectPHIDs;
private $limit;
private $needVectors;
private $keyMap = array();
private $dimensionMap = array();
public function withFacts(array $facts) {
$this->facts = $facts;
return $this;
}
public function withObjectPHIDs(array $object_phids) {
$this->objectPHIDs = $object_phids;
return $this;
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function needVectors($need) {
$this->needVectors = $need;
return $this;
}
public function execute() {
$facts = mpull($this->facts, null, 'getKey');
if (!$facts) {
throw new Exception(pht('Executing a fact query requires facts.'));
}
$table_map = array();
foreach ($facts as $fact) {
$datapoint = $fact->newDatapoint();
$table = $datapoint->getTableName();
if (!isset($table_map[$table])) {
$table_map[$table] = array(
'table' => $datapoint,
'facts' => array(),
);
}
$table_map[$table]['facts'][] = $fact;
}
$rows = array();
foreach ($table_map as $spec) {
$rows[] = $this->executeWithTable($spec);
}
$rows = array_mergev($rows);
$key_unmap = array_flip($this->keyMap);
$dimension_unmap = array_flip($this->dimensionMap);
$groups = array();
$need_phids = array();
foreach ($rows as $row) {
$groups[$row['keyID']][] = $row;
$object_id = $row['objectID'];
if (!isset($dimension_unmap[$object_id])) {
$need_phids[$object_id] = $object_id;
}
$dimension_id = $row['dimensionID'];
if ($dimension_id && !isset($dimension_unmap[$dimension_id])) {
$need_phids[$dimension_id] = $dimension_id;
}
}
$dimension_unmap += id(new PhabricatorFactObjectDimension())
->newDimensionUnmap($need_phids);
$results = array();
foreach ($groups as $key_id => $rows) {
$key = $key_unmap[$key_id];
$fact = $facts[$key];
$datapoint = $fact->newDatapoint();
foreach ($rows as $row) {
$dimension_id = $row['dimensionID'];
if ($dimension_id) {
if (!isset($dimension_unmap[$dimension_id])) {
continue;
} else {
$dimension_phid = $dimension_unmap[$dimension_id];
}
} else {
$dimension_phid = null;
}
$object_id = $row['objectID'];
if (!isset($dimension_unmap[$object_id])) {
continue;
} else {
$object_phid = $dimension_unmap[$object_id];
}
$result = array(
'key' => $key,
'objectPHID' => $object_phid,
'dimensionPHID' => $dimension_phid,
'value' => (int)$row['value'],
'epoch' => $row['epoch'],
);
if ($this->needVectors) {
$result['vector'] = $datapoint->newRawVector($result);
}
$results[] = $result;
}
}
return $results;
}
private function executeWithTable(array $spec) {
$table = $spec['table'];
$facts = $spec['facts'];
$conn = $table->establishConnection('r');
$fact_keys = mpull($facts, 'getKey');
$this->keyMap = id(new PhabricatorFactKeyDimension())
->newDimensionMap($fact_keys);
if (!$this->keyMap) {
return array();
}
$where = array();
$where[] = qsprintf(
$conn,
'keyID IN (%Ld)',
$this->keyMap);
if ($this->objectPHIDs) {
$object_map = id(new PhabricatorFactObjectDimension())
->newDimensionMap($this->objectPHIDs);
if (!$object_map) {
return array();
}
$this->dimensionMap = $object_map;
$where[] = qsprintf(
$conn,
'objectID IN (%Ld)',
$this->dimensionMap);
}
$where = '('.implode(') AND (', $where).')';
if ($this->limit) {
$limit = qsprintf(
$conn,
'LIMIT %d',
$this->limit);
} else {
$limit = '';
}
return queryfx_all(
$conn,
'SELECT keyID, objectID, dimensionID, value, epoch
FROM %T WHERE %Q %Q',
$table->getTableName(),
$where,
$limit);
}
}

View file

@ -1,38 +0,0 @@
<?php
final class PhabricatorFactSimpleSpec extends PhabricatorFactSpec {
private $type;
private $name;
private $unit;
public function __construct($type) {
$this->type = $type;
}
public function getType() {
return $this->type;
}
public function setUnit($unit) {
$this->unit = $unit;
return $this;
}
public function getUnit() {
return $this->unit;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
if ($this->name !== null) {
return $this->name;
}
return parent::getName();
}
}

View file

@ -1,53 +0,0 @@
<?php
abstract class PhabricatorFactSpec extends Phobject {
const UNIT_COUNT = 'unit-count';
const UNIT_EPOCH = 'unit-epoch';
public static function newSpecsForFactTypes(
array $engines,
array $fact_types) {
assert_instances_of($engines, 'PhabricatorFactEngine');
$map = array();
foreach ($engines as $engine) {
$specs = $engine->getFactSpecs($fact_types);
$specs = mpull($specs, null, 'getType');
$map += $specs;
}
foreach ($fact_types as $type) {
if (empty($map[$type])) {
$map[$type] = new PhabricatorFactSimpleSpec($type);
}
}
return $map;
}
abstract public function getType();
public function getUnit() {
return null;
}
public function getName() {
return pht(
'Fact (%s)',
$this->getType());
}
public function formatValueForDisplay(PhabricatorUser $user, $value) {
$unit = $this->getUnit();
switch ($unit) {
case self::UNIT_COUNT:
return number_format($value);
case self::UNIT_EPOCH:
return phabricator_datetime($value, $user);
default:
return $value;
}
}
}

View file

@ -0,0 +1,110 @@
<?php
abstract class PhabricatorFactDimension extends PhabricatorFactDAO {
abstract protected function getDimensionColumnName();
final public function newDimensionID($key, $create = false) {
$map = $this->newDimensionMap(array($key), $create);
return idx($map, $key);
}
final public function newDimensionUnmap(array $ids) {
if (!$ids) {
return array();
}
$conn = $this->establishConnection('r');
$column = $this->getDimensionColumnName();
$rows = queryfx_all(
$conn,
'SELECT id, %C FROM %T WHERE id IN (%Ld)',
$column,
$this->getTableName(),
$ids);
$rows = ipull($rows, $column, 'id');
return $rows;
}
final public function newDimensionMap(array $keys, $create = false) {
if (!$keys) {
return array();
}
$conn = $this->establishConnection('r');
$column = $this->getDimensionColumnName();
$rows = queryfx_all(
$conn,
'SELECT id, %C FROM %T WHERE %C IN (%Ls)',
$column,
$this->getTableName(),
$column,
$keys);
$rows = ipull($rows, 'id', $column);
$map = array();
$need = array();
foreach ($keys as $key) {
if (isset($rows[$key])) {
$map[$key] = (int)$rows[$key];
} else {
$need[] = $key;
}
}
if (!$need) {
return $map;
}
if (!$create) {
return $map;
}
$sql = array();
foreach ($need as $key) {
$sql[] = qsprintf(
$conn,
'(%s)',
$key);
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
queryfx(
$conn,
'INSERT IGNORE INTO %T (%C) VALUES %Q',
$this->getTableName(),
$column,
$chunk);
}
unset($unguarded);
$rows = queryfx_all(
$conn,
'SELECT id, %C FROM %T WHERE %C IN (%Ls)',
$column,
$this->getTableName(),
$column,
$need);
$rows = ipull($rows, 'id', $column);
foreach ($need as $key) {
if (isset($rows[$key])) {
$map[$key] = (int)$rows[$key];
} else {
throw new Exception(
pht(
'Failed to load or generate dimension ID ("%s") for dimension '.
'key "%s".',
get_class($this),
$key));
}
}
return $map;
}
}

View file

@ -0,0 +1,87 @@
<?php
final class PhabricatorFactIntDatapoint extends PhabricatorFactDAO {
protected $keyID;
protected $objectID;
protected $dimensionID;
protected $value;
protected $epoch;
private $key;
private $objectPHID;
private $dimensionPHID;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'id' => 'auto64',
'dimensionID' => 'id?',
'value' => 'sint64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_dimension' => array(
'columns' => array('keyID', 'dimensionID'),
),
'key_object' => array(
'columns' => array('objectID'),
),
),
) + parent::getConfiguration();
}
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setDimensionPHID($dimension_phid) {
$this->dimensionPHID = $dimension_phid;
return $this;
}
public function getDimensionPHID() {
return $this->dimensionPHID;
}
public function newDatapointVector() {
return $this->formatVector(
array(
$this->key,
$this->objectPHID,
$this->dimensionPHID,
$this->value,
$this->epoch,
));
}
public function newRawVector(array $spec) {
return $this->formatVector(
array(
$spec['key'],
$spec['objectPHID'],
$spec['dimensionPHID'],
$spec['value'],
$spec['epoch'],
));
}
private function formatVector(array $vector) {
return implode(':', $vector);
}
}

View file

@ -0,0 +1,27 @@
<?php
final class PhabricatorFactKeyDimension
extends PhabricatorFactDimension {
protected $factKey;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'factKey' => 'text64',
),
self::CONFIG_KEY_SCHEMA => array(
'key_factkey' => array(
'columns' => array('factKey'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
protected function getDimensionColumnName() {
return 'factKey';
}
}

View file

@ -0,0 +1,25 @@
<?php
final class PhabricatorFactObjectDimension
extends PhabricatorFactDimension {
protected $objectPHID;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(),
self::CONFIG_KEY_SCHEMA => array(
'key_object' => array(
'columns' => array('objectPHID'),
'unique' => true,
),
),
) + parent::getConfiguration();
}
protected function getDimensionColumnName() {
return 'objectPHID';
}
}

View file

@ -20,7 +20,6 @@ final class PhabricatorImageRemarkupRule extends PhutilRemarkupRule {
$defaults = array(
'uri' => null,
'alt' => null,
'href' => null,
'width' => null,
'height' => null,
);
@ -45,10 +44,6 @@ final class PhabricatorImageRemarkupRule extends PhutilRemarkupRule {
$args += $defaults;
if ($args['href'] && !PhabricatorEnv::isValidURIForLink($args['href'])) {
$args['href'] = null;
}
if ($args['uri']) {
$src_uri = id(new PhutilURI('/file/imageproxy/'))
->setQueryParam('uri', (string)$args['uri']);
@ -57,10 +52,9 @@ final class PhabricatorImageRemarkupRule extends PhutilRemarkupRule {
array(
'src' => $src_uri,
'alt' => $args['alt'],
'href' => $args['href'],
'width' => $args['width'],
'height' => $args['height'],
));
));
return $this->getEngine()->storeText($img);
} else {
return $matches[0];

View file

@ -81,6 +81,7 @@ final class HarbormasterURIArtifact extends HarbormasterArtifact {
array(
'href' => $uri,
'target' => '_blank',
'rel' => 'noreferrer',
),
$name);
}

View file

@ -1302,6 +1302,11 @@ final class PhabricatorMetaMTAMail
$headers[] = array('Thread-Topic', $related_phid);
}
$headers[] = array('X-Phabricator-Mail-ID', $this->getID());
$unique = Filesystem::readRandomCharacters(16);
$headers[] = array('X-Phabricator-Send-Attempt', $unique);
return $headers;
}
@ -1356,6 +1361,8 @@ final class PhabricatorMetaMTAMail
'X-Phabricator-Sent-This-Message',
'X-Phabricator-Must-Encrypt',
'X-Phabricator-Mail-ID',
'X-Phabricator-Send-Attempt',
);
// NOTE: The major header we want to drop is "X-Phabricator-Mail-Tags".

View file

@ -309,6 +309,8 @@ final class NuanceGitHubEventItemType
'a',
array(
'href' => $event_uri,
'target' => '_blank',
'rel' => 'noreferrer',
),
$event_uri);
}

View file

@ -10,6 +10,9 @@ final class PhrictionDocumentQuery
private $slugPrefix;
private $statuses;
private $parentPaths;
private $ancestorPaths;
private $needContent;
const ORDER_HIERARCHY = 'hierarchy';
@ -34,7 +37,7 @@ final class PhrictionDocumentQuery
return $this;
}
public function withSlugPrefix($slug_prefix) {
public function withSlugPrefix($slug_prefix) {
$this->slugPrefix = $slug_prefix;
return $this;
}
@ -44,6 +47,16 @@ final class PhrictionDocumentQuery
return $this;
}
public function withParentPaths(array $paths) {
$this->parentPaths = $paths;
return $this;
}
public function withAncestorPaths(array $paths) {
$this->ancestorPaths = $paths;
return $this;
}
public function needContent($need_content) {
$this->needContent = $need_content;
return $this;
@ -214,6 +227,94 @@ final class PhrictionDocumentQuery
$this->depths);
}
if ($this->parentPaths !== null || $this->ancestorPaths !== null) {
$sets = array(
array(
'paths' => $this->parentPaths,
'parents' => true,
),
array(
'paths' => $this->ancestorPaths,
'parents' => false,
),
);
$paths = array();
foreach ($sets as $set) {
$set_paths = $set['paths'];
if ($set_paths === null) {
continue;
}
if (!$set_paths) {
throw new PhabricatorEmptyQueryException(
pht('No parent/ancestor paths specified.'));
}
$is_parents = $set['parents'];
foreach ($set_paths as $path) {
$path_normal = PhabricatorSlug::normalize($path);
if ($path !== $path_normal) {
throw new Exception(
pht(
'Document path "%s" is not a valid path. The normalized '.
'form of this path is "%s".',
$path,
$path_normal));
}
$depth = PhabricatorSlug::getDepth($path_normal);
if ($is_parents) {
$min_depth = $depth + 1;
$max_depth = $depth + 1;
} else {
$min_depth = $depth + 1;
$max_depth = null;
}
$paths[] = array(
$path_normal,
$min_depth,
$max_depth,
);
}
}
$path_clauses = array();
foreach ($paths as $path) {
$parts = array();
list($prefix, $min, $max) = $path;
// If we're getting children or ancestors of the root document, they
// aren't actually stored with the leading "/" in the database, so
// just skip this part of the clause.
if ($prefix !== '/') {
$parts[] = qsprintf(
$conn,
'd.slug LIKE %>',
$prefix);
}
if ($min !== null) {
$parts[] = qsprintf(
$conn,
'd.depth >= %d',
$min);
}
if ($max !== null) {
$parts[] = qsprintf(
$conn,
'd.depth <= %d',
$max);
}
$path_clauses[] = '('.implode(') AND (', $parts).')';
}
$where[] = '('.implode(') OR (', $path_clauses).')';
}
return $where;
}

View file

@ -27,6 +27,14 @@ final class PhrictionDocumentSearchEngine
$query->withSlugs($map['paths']);
}
if ($map['parentPaths']) {
$query->withParentPaths($map['parentPaths']);
}
if ($map['ancestorPaths']) {
$query->withAncestorPaths($map['ancestorPaths']);
}
return $query;
}
@ -40,6 +48,14 @@ final class PhrictionDocumentSearchEngine
->setKey('paths')
->setIsHidden(true)
->setLabel(pht('Paths')),
id(new PhabricatorSearchStringListField())
->setKey('parentPaths')
->setIsHidden(true)
->setLabel(pht('Parent Paths')),
id(new PhabricatorSearchStringListField())
->setKey('ancestorPaths')
->setIsHidden(true)
->setLabel(pht('Ancestor Paths')),
);
}

View file

@ -64,6 +64,7 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule {
array(
'href' => $uri,
'target' => '_blank',
'rel' => 'noreferrer',
),
$name);
}

View file

@ -18,12 +18,6 @@ final class PhabricatorPonderApplication extends PhabricatorApplication {
return 'fa-university';
}
public function getFactObjectsForAnalysis() {
return array(
new PonderQuestion(),
);
}
public function getTitleGlyph() {
return "\xE2\x97\xB3";
}

View file

@ -99,7 +99,8 @@ final class PhabricatorLinkProfileMenuItem
->setHref($href)
->setName($name)
->setIcon($icon_class)
->setTooltip($tooltip);
->setTooltip($tooltip)
->setRel('noreferrer');
return array(
$item,

View file

@ -0,0 +1,12 @@
<?php
final class PhabricatorFiletreeWidthSetting
extends PhabricatorInternalSetting {
const SETTINGKEY = 'filetree.width';
public function getSettingName() {
return pht('Filetree Width');
}
}

View file

@ -260,6 +260,11 @@ abstract class PhabricatorApplicationTransaction
return $this->oldValueHasBeenSet;
}
public function newChronologicalSortVector() {
return id(new PhutilSortVector())
->addInt((int)$this->getDateCreated())
->addInt((int)$this->getID());
}
/* -( Rendering )---------------------------------------------------------- */

View file

@ -19,9 +19,25 @@ final class PhabricatorTypeaheadFunctionHelpController
return new Aphront404Response();
}
$source = $sources[$class];
$raw_parameters = $request->getStr('parameters');
if ($raw_parameters) {
$parameters = phutil_json_decode($raw_parameters);
} else {
$parameters = array();
}
$source = id(clone $sources[$class])
->setParameters($parameters);
// This can fail for some types of datasources (like the custom field proxy
// datasources) if the "parameters" are wrong. Just fail cleanly instead
// of fataling.
try {
$application_class = $source->getDatasourceApplicationClass();
} catch (Exception $ex) {
return new Aphront404Response();
}
$application_class = $source->getDatasourceApplicationClass();
if ($application_class) {
$result = id(new PhabricatorApplicationQuery())
->setViewer($this->getViewer())

View file

@ -226,6 +226,12 @@ final class PhabricatorTypeaheadModularDatasourceController
if ($source->getAllDatasourceFunctions()) {
$reference_uri = '/typeahead/help/'.get_class($source).'/';
$parameters = $source->getParameters();
if ($parameters) {
$reference_uri = (string)id(new PhutilURI($reference_uri))
->setQueryParam('parameters', phutil_json_encode($parameters));
}
$reference_link = phutil_tag(
'a',
array(

View file

@ -0,0 +1,58 @@
<?php
abstract class PhabricatorTypeaheadProxyDatasource
extends PhabricatorTypeaheadCompositeDatasource {
private $datasource;
public function setDatasource(PhabricatorTypeaheadDatasource $datasource) {
$this->datasource = $datasource;
$this->setParameters(
array(
'class' => get_class($datasource),
'parameters' => $datasource->getParameters(),
));
return $this;
}
public function getDatasource() {
if (!$this->datasource) {
$class = $this->getParameter('class');
$parent = 'PhabricatorTypeaheadDatasource';
if (!is_subclass_of($class, $parent)) {
throw new Exception(
pht(
'Configured datasource class "%s" must be a valid subclass of '.
'"%s".',
$class,
$parent));
}
$datasource = newv($class, array());
$datasource->setParameters($this->getParameter('parameters', array()));
$this->datasource = $datasource;
}
return $this->datasource;
}
public function getComponentDatasources() {
return array(
$this->getDatasource(),
);
}
public function getDatasourceApplicationClass() {
return $this->getDatasource()->getDatasourceApplicationClass();
}
public function getBrowseTitle() {
return $this->getDatasource()->getBrowseTitle();
}
public function getPlaceholderText() {
return $this->getDatasource()->getPlaceholderText();
}
}

View file

@ -0,0 +1,70 @@
<?php
final class PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse Any');
}
public function getPlaceholderText() {
return pht('Type "any()"...');
}
public function getDatasourceApplicationClass() {
return null;
}
public function getDatasourceFunctions() {
return array(
'any' => array(
'name' => pht('Any Value'),
'summary' => pht('Find results with any value.'),
'description' => pht(
"This function includes results which have any value. Use a query ".
"like this to find results with any value:\n\n%s",
'> any()'),
),
);
}
public function loadResults() {
$results = array(
$this->newAnyFunction(),
);
return $this->filterResultsAgainstTokens($results);
}
protected function evaluateFunction($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_ANY,
null);
}
return $results;
}
public function renderFunctionTokens($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
$this->newAnyFunction());
}
return $results;
}
private function newAnyFunction() {
$name = pht('Any Value');
return $this->newFunctionResult()
->setName($name.' any')
->setDisplayName($name)
->setIcon('fa-circle-o')
->setPHID('any()')
->setUnique(true)
->addAttribute(pht('Select results with any value.'));
}
}

View file

@ -0,0 +1,17 @@
<?php
final class PhabricatorCustomFieldApplicationSearchDatasource
extends PhabricatorTypeaheadProxyDatasource {
public function getComponentDatasources() {
$datasources = parent::getComponentDatasources();
$datasources[] =
new PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource();
$datasources[] =
new PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource();
return $datasources;
}
}

View file

@ -0,0 +1,72 @@
<?php
final class PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource
extends PhabricatorTypeaheadDatasource {
public function getBrowseTitle() {
return pht('Browse No Value');
}
public function getPlaceholderText() {
return pht('Type "none()"...');
}
public function getDatasourceApplicationClass() {
return null;
}
public function getDatasourceFunctions() {
return array(
'none' => array(
'name' => pht('No Value'),
'summary' => pht('Find results with no value.'),
'description' => pht(
"This function includes results which have no value. Use a query ".
"like this to find results with no value:\n\n%s\n\n",
'If you combine this function with other constraints, results '.
'which have no value or the specified values will be returned.',
'> any()'),
),
);
}
public function loadResults() {
$results = array(
$this->newNoneFunction(),
);
return $this->filterResultsAgainstTokens($results);
}
protected function evaluateFunction($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = new PhabricatorQueryConstraint(
PhabricatorQueryConstraint::OPERATOR_NULL,
null);
}
return $results;
}
public function renderFunctionTokens($function, array $argv_list) {
$results = array();
foreach ($argv_list as $argv) {
$results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult(
$this->newNoneFunction());
}
return $results;
}
private function newNoneFunction() {
$name = pht('No Value');
return $this->newFunctionResult()
->setName($name.' none')
->setDisplayName($name)
->setIcon('fa-ban')
->setPHID('none()')
->setUnique(true)
->addAttribute(pht('Select results with no value.'));
}
}

View file

@ -31,7 +31,11 @@ final class PhabricatorStandardCustomFieldLink
return phutil_tag(
'a',
array('href' => $value, 'target' => '_blank'),
array(
'href' => $value,
'target' => '_blank',
'rel' => 'noreferrer',
),
$value);
}

View file

@ -33,12 +33,28 @@ abstract class PhabricatorStandardCustomFieldTokenizer
$control = id(new AphrontFormTokenizerControl())
->setLabel($this->getFieldName())
->setName($this->getFieldKey())
->setDatasource($this->getDatasource())
->setDatasource($this->newApplicationSearchDatasource())
->setValue(nonempty($value, array()));
$form->appendControl($control);
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
if ($value) {
$datasource = $this->newApplicationSearchDatasource()
->setViewer($this->getViewer());
$value = $datasource->evaluateTokens($value);
$query->withApplicationSearchContainsConstraint(
$this->newStringIndex(null),
$value);
}
}
public function getHeraldFieldValueType($condition) {
return id(new HeraldTokenizerFieldValue())
->setKey('custom.'.$this->getFieldKey())
@ -120,4 +136,11 @@ abstract class PhabricatorStandardCustomFieldTokenizer
}
}
protected function newApplicationSearchDatasource() {
$datasource = $this->getDatasource();
return id(new PhabricatorCustomFieldApplicationSearchDatasource())
->setDatasource($datasource);
}
}

View file

@ -9,6 +9,7 @@ final class PhabricatorQueryConstraint extends Phobject {
const OPERATOR_ANCESTOR = 'ancestor';
const OPERATOR_EMPTY = 'empty';
const OPERATOR_ONLY = 'only';
const OPERATOR_ANY = 'any';
private $operator;
private $value;

View file

@ -342,6 +342,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$where[] = $this->buildSpacesWhereClause($conn);
$where[] = $this->buildNgramsWhereClause($conn);
$where[] = $this->buildFerretWhereClause($conn);
$where[] = $this->buildApplicationSearchWhereClause($conn);
return $where;
}
@ -1158,12 +1159,29 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
PhabricatorCustomFieldIndexStorage $index,
$value) {
$values = (array)$value;
$data_values = array();
$constraint_values = array();
foreach ($values as $value) {
if ($value instanceof PhabricatorQueryConstraint) {
$constraint_values[] = $value;
} else {
$data_values[] = $value;
}
}
$alias = 'appsearch_'.count($this->applicationSearchConstraints);
$this->applicationSearchConstraints[] = array(
'type' => $index->getIndexValueType(),
'cond' => '=',
'table' => $index->getTableName(),
'index' => $index->getIndexKey(),
'value' => $value,
'alias' => $alias,
'value' => $values,
'data' => $data_values,
'constraints' => $constraint_values,
);
return $this;
@ -1203,11 +1221,14 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
'int'));
}
$alias = 'appsearch_'.count($this->applicationSearchConstraints);
$this->applicationSearchConstraints[] = array(
'type' => $index->getIndexValueType(),
'cond' => 'range',
'table' => $index->getTableName(),
'index' => $index->getIndexKey(),
'alias' => $alias,
'value' => array($min, $max),
);
@ -1256,7 +1277,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
switch ($type) {
case 'string':
case 'int':
if (count((array)$value) > 1) {
if (count($value) > 1) {
return true;
}
break;
@ -1309,49 +1330,39 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
* @task appsearch
*/
protected function buildApplicationSearchJoinClause(
AphrontDatabaseConnection $conn_r) {
AphrontDatabaseConnection $conn) {
$joins = array();
foreach ($this->applicationSearchConstraints as $key => $constraint) {
$table = $constraint['table'];
$alias = 'appsearch_'.$key;
$alias = $constraint['alias'];
$index = $constraint['index'];
$cond = $constraint['cond'];
$phid_column = $this->getApplicationSearchObjectPHIDColumn();
switch ($cond) {
case '=':
$type = $constraint['type'];
switch ($type) {
case 'string':
$constraint_clause = qsprintf(
$conn_r,
'%T.indexValue IN (%Ls)',
$alias,
(array)$constraint['value']);
// Figure out whether we need to do a LEFT JOIN or not. We need to
// LEFT JOIN if we're going to select "IS NULL" rows.
$join_type = 'JOIN';
foreach ($constraint['constraints'] as $query_constraint) {
$op = $query_constraint->getOperator();
if ($op === PhabricatorQueryConstraint::OPERATOR_NULL) {
$join_type = 'LEFT JOIN';
break;
case 'int':
$constraint_clause = qsprintf(
$conn_r,
'%T.indexValue IN (%Ld)',
$alias,
(array)$constraint['value']);
break;
default:
throw new Exception(pht('Unknown index type "%s"!', $type));
}
}
$joins[] = qsprintf(
$conn_r,
'JOIN %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s
AND (%Q)',
$conn,
'%Q %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s',
$join_type,
$table,
$alias,
$alias,
$phid_column,
$alias,
$index,
$constraint_clause);
$index);
break;
case 'range':
list($min, $max) = $constraint['value'];
@ -1362,19 +1373,19 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
if ($min === null) {
$constraint_clause = qsprintf(
$conn_r,
$conn,
'%T.indexValue <= %d',
$alias,
$max);
} else if ($max === null) {
$constraint_clause = qsprintf(
$conn_r,
$conn,
'%T.indexValue >= %d',
$alias,
$min);
} else {
$constraint_clause = qsprintf(
$conn_r,
$conn,
'%T.indexValue BETWEEN %d AND %d',
$alias,
$min,
@ -1382,7 +1393,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
}
$joins[] = qsprintf(
$conn_r,
$conn,
'JOIN %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s
AND (%Q)',
@ -1414,7 +1425,7 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
$key = $spec['customfield.index.key'];
$joins[] = qsprintf(
$conn_r,
$conn,
'LEFT JOIN %T %T ON %T.objectPHID = %Q
AND %T.indexKey = %s',
$table,
@ -1428,6 +1439,88 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery
return implode(' ', $joins);
}
/**
* Construct a WHERE clause appropriate for applying ApplicationSearch
* constraints.
*
* @param AphrontDatabaseConnection Connection executing the query.
* @return list<string> Where clause parts.
* @task appsearch
*/
protected function buildApplicationSearchWhereClause(
AphrontDatabaseConnection $conn) {
$where = array();
foreach ($this->applicationSearchConstraints as $key => $constraint) {
$alias = $constraint['alias'];
$cond = $constraint['cond'];
$type = $constraint['type'];
$data_values = $constraint['data'];
$constraint_values = $constraint['constraints'];
$constraint_parts = array();
switch ($cond) {
case '=':
if ($data_values) {
switch ($type) {
case 'string':
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IN (%Ls)',
$alias,
$data_values);
break;
case 'int':
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IN (%Ld)',
$alias,
$data_values);
break;
default:
throw new Exception(pht('Unknown index type "%s"!', $type));
}
}
if ($constraint_values) {
foreach ($constraint_values as $value) {
$op = $value->getOperator();
switch ($op) {
case PhabricatorQueryConstraint::OPERATOR_NULL:
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IS NULL',
$alias);
break;
case PhabricatorQueryConstraint::OPERATOR_ANY:
$constraint_parts[] = qsprintf(
$conn,
'%T.indexValue IS NOT NULL',
$alias);
break;
default:
throw new Exception(
pht(
'No support for applying operator "%s" against '.
'index of type "%s".',
$op,
$type));
}
}
}
if ($constraint_parts) {
$where[] = '('.implode(') OR (', $constraint_parts).')';
}
break;
}
}
return $where;
}
/* -( Integration with CustomField )--------------------------------------- */

View file

@ -30,6 +30,7 @@ final class AphrontSideNavFilterView extends AphrontView {
private $mainID;
private $isProfileMenu;
private $footer = array();
private $width;
public function setMenuID($menu_id) {
$this->menuID = $menu_id;
@ -82,6 +83,11 @@ final class AphrontSideNavFilterView extends AphrontView {
return $this;
}
public function setWidth($width) {
$this->width = $width;
return $this;
}
public function getMenuView() {
return $this->menu;
}
@ -216,6 +222,24 @@ final class AphrontSideNavFilterView extends AphrontView {
$local_menu = null;
$main_id = $this->getMainID();
$width = $this->width;
if ($width) {
$width = min($width, 600);
$width = max($width, 150);
} else {
$width = null;
}
if ($width && !$this->collapsed) {
$width_drag_style = 'left: '.$width.'px';
$width_panel_style = 'width: '.$width.'px';
$width_margin_style = 'margin-left: '.($width + 7).'px';
} else {
$width_drag_style = null;
$width_panel_style = null;
$width_margin_style = null;
}
if ($this->flexible) {
$drag_id = celerity_generate_unique_node_id();
$flex_bar = phutil_tag(
@ -223,6 +247,7 @@ final class AphrontSideNavFilterView extends AphrontView {
array(
'class' => 'phabricator-nav-drag',
'id' => $drag_id,
'style' => $width_drag_style,
),
'');
} else {
@ -238,14 +263,14 @@ final class AphrontSideNavFilterView extends AphrontView {
$nav_classes[] = 'has-local-nav';
}
$local_menu =
phutil_tag(
'div',
array(
'class' => 'phabricator-nav-local phabricator-side-menu',
'id' => $local_id,
),
$this->menu->setID($this->getMenuID()));
$local_menu = phutil_tag(
'div',
array(
'class' => 'phabricator-nav-local phabricator-side-menu',
'id' => $local_id,
'style' => $width_panel_style,
),
$this->menu->setID($this->getMenuID()));
}
$crumbs = null;
@ -264,12 +289,13 @@ final class AphrontSideNavFilterView extends AphrontView {
Javelin::initBehavior(
'phabricator-nav',
array(
'mainID' => $main_id,
'localID' => $local_id,
'dragID' => $drag_id,
'contentID' => $content_id,
'backgroundID' => $background_id,
'collapsed' => $this->collapsed,
'mainID' => $main_id,
'localID' => $local_id,
'dragID' => $drag_id,
'contentID' => $content_id,
'backgroundID' => $background_id,
'collapsed' => $this->collapsed,
'width' => $width,
));
if ($this->active) {
@ -297,6 +323,7 @@ final class AphrontSideNavFilterView extends AphrontView {
array(
'class' => 'phabricator-nav-content plb',
'id' => $content_id,
'style' => $width_margin_style,
),
array(
$crumbs,

View file

@ -255,8 +255,10 @@ final class PhabricatorActionView extends AphrontView {
} else {
if ($this->getOpenInNewWindow()) {
$target = '_blank';
$rel = 'noreferrer';
} else {
$target = null;
$rel = null;
}
if ($this->submenu) {
@ -277,6 +279,7 @@ final class PhabricatorActionView extends AphrontView {
'href' => $this->getHref(),
'class' => 'phabricator-action-view-item',
'target' => $target,
'rel' => $rel,
'sigil' => $sigils,
'meta' => $this->metadata,
),

View file

@ -34,6 +34,7 @@ final class PHUIListItemView extends AphrontTagView {
private $actionIcon;
private $actionIconHref;
private $count;
private $rel;
public function setOpenInNewWindow($open_in_new_window) {
$this->openInNewWindow = $open_in_new_window;
@ -44,7 +45,16 @@ final class PHUIListItemView extends AphrontTagView {
return $this->openInNewWindow;
}
public function setHideInApplicationMenu($hide) {
public function setRel($rel) {
$this->rel = $rel;
return $this;
}
public function getRel() {
return $this->rel;
}
public function setHideInApplicationMenu($hide) {
$this->hideInApplicationMenu = $hide;
return $this;
}
@ -363,6 +373,7 @@ final class PHUIListItemView extends AphrontTagView {
'meta' => $meta,
'sigil' => $sigil,
'target' => $this->getOpenInNewWindow() ? '_blank' : null,
'rel' => $this->rel,
),
array(
$aural,

View file

@ -154,25 +154,30 @@ final class PHUITagView extends AphrontTagView {
$classes[] = 'phui-tag-'.$this->border;
}
if ($this->phid) {
Javelin::initBehavior('phui-hovercards');
$attributes = array(
'href' => $this->href,
'class' => $classes,
);
$attributes = array(
'href' => $this->href,
'sigil' => 'hovercard',
'meta' => array(
'hoverPHID' => $this->phid,
),
'target' => $this->external ? '_blank' : null,
);
} else {
$attributes = array(
'href' => $this->href,
'target' => $this->external ? '_blank' : null,
if ($this->external) {
$attributes += array(
'target' => '_blank',
'rel' => 'noreferrer',
);
}
return $attributes + array('class' => $classes);
if ($this->phid) {
Javelin::initBehavior('phui-hovercards');
$attributes += array(
'sigil' => 'hovercard',
'meta' => array(
'hoverPHID' => $this->phid,
),
);
}
return $attributes;
}
protected function getTagContent() {

View file

@ -44,7 +44,7 @@
position: fixed;
top: 0;
bottom: 0;
left: 410px;
left: 310px;
width: 7px;
cursor: col-resize;
@ -66,7 +66,7 @@
.device-desktop .phabricator-standard-page-body .has-drag-nav
.phabricator-nav-content {
margin-left: 417px;
margin-left: 317px;
}
.device-desktop .phabricator-standard-page-body .has-drag-nav
@ -81,7 +81,7 @@
}
.device-desktop .phui-navigation-shell .has-drag-nav .phabricator-nav-local {
width: 410px;
width: 310px;
padding: 0;
background: transparent;
}

View file

@ -18,7 +18,6 @@ JX.behavior('phabricator-nav', function(config) {
var main = JX.$(config.mainID);
var drag = JX.$(config.dragID);
// - Flexible Navigation Column ------------------------------------------------
@ -98,22 +97,52 @@ JX.behavior('phabricator-nav', function(config) {
}
JX.DOM.alterClass(document.body, 'jx-drag-col', false);
dragging = false;
new JX.Request('/settings/adjust/', JX.bag)
.setData(
{
key: 'filetree.width',
value: JX.$V(drag).x
})
.send();
});
function resetdrag() {
var saved_width = config.width;
function savedrag() {
saved_width = JX.$V(drag).x;
local.style.width = '';
drag.style.left = '';
content.style.marginLeft = '';
}
function restoredrag() {
if (!saved_width) {
return;
}
local.style.width = saved_width + 'px';
drag.style.left = saved_width + 'px';
content.style.marginLeft = (saved_width + JX.Vector.getDim(drag).x) + 'px';
}
var collapsed = config.collapsed;
JX.Stratcom.listen('differential-filetree-toggle', null, function() {
collapsed = !collapsed;
if (collapsed) {
savedrag();
}
JX.DOM.alterClass(main, 'has-local-nav', !collapsed);
JX.DOM.alterClass(main, 'has-drag-nav', !collapsed);
JX.DOM.alterClass(main, 'has-closed-nav', collapsed);
resetdrag();
if (!collapsed) {
restoredrag();
}
new JX.Request('/settings/adjust/', JX.bag)
.setData({ key : 'nav-collapsed', value : (collapsed ? 1 : 0) })
.send();