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:
commit
3f2419eebb
62 changed files with 2186 additions and 566 deletions
|
@ -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',
|
||||
|
|
5
resources/sql/autopatches/20180218.fact.01.dim.key.sql
Normal file
5
resources/sql/autopatches/20180218.fact.01.dim.key.sql
Normal 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};
|
5
resources/sql/autopatches/20180218.fact.02.dim.obj.sql
Normal file
5
resources/sql/autopatches/20180218.fact.02.dim.obj.sql
Normal 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};
|
8
resources/sql/autopatches/20180218.fact.03.data.int.sql
Normal file
8
resources/sql/autopatches/20180218.fact.03.data.int.sql
Normal 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};
|
|
@ -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',
|
||||
|
|
|
@ -77,6 +77,7 @@ final class PhabricatorAuthAccountView extends AphrontView {
|
|||
array(
|
||||
'href' => $account_uri,
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer',
|
||||
),
|
||||
$account_uri);
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ final class PhabricatorCalendarICSURIImportEngine
|
|||
array(
|
||||
'href' => $uri,
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer',
|
||||
),
|
||||
$uri);
|
||||
}
|
||||
|
|
|
@ -35,12 +35,6 @@ final class PhabricatorDifferentialApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getFactObjectsForAnalysis() {
|
||||
return array(
|
||||
new DifferentialRevision(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getTitleGlyph() {
|
||||
return "\xE2\x9A\x99";
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -39,12 +39,6 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
);
|
||||
}
|
||||
|
||||
public function getFactObjectsForAnalysis() {
|
||||
return array(
|
||||
new PhabricatorRepositoryCommit(),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new DiffusionCommitRemarkupRule(),
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -31,6 +31,7 @@ final class PhabricatorFactApplication extends PhabricatorApplication {
|
|||
'/fact/' => array(
|
||||
'' => 'PhabricatorFactHomeController',
|
||||
'chart/' => 'PhabricatorFactChartController',
|
||||
'object/(?<phid>[^/]+)/' => 'PhabricatorFactObjectController',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
9
src/applications/fact/fact/PhabricatorCountFact.php
Normal file
9
src/applications/fact/fact/PhabricatorCountFact.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCountFact extends PhabricatorFact {
|
||||
|
||||
protected function newTemplateDatapoint() {
|
||||
return new PhabricatorFactIntDatapoint();
|
||||
}
|
||||
|
||||
}
|
40
src/applications/fact/fact/PhabricatorFact.php
Normal file
40
src/applications/fact/fact/PhabricatorFact.php
Normal 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();
|
||||
|
||||
}
|
9
src/applications/fact/fact/PhabricatorPointsFact.php
Normal file
9
src/applications/fact/fact/PhabricatorPointsFact.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPointsFact extends PhabricatorFact {
|
||||
|
||||
protected function newTemplateDatapoint() {
|
||||
return new PhabricatorFactIntDatapoint();
|
||||
}
|
||||
|
||||
}
|
|
@ -58,10 +58,6 @@ final class PhabricatorFactManagementAnalyzeWorkflow
|
|||
}
|
||||
}
|
||||
|
||||
if (!$args->getArg('skip-aggregates')) {
|
||||
$daemon->processAggregates();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
181
src/applications/fact/query/PhabricatorFactDatapointQuery.php
Normal file
181
src/applications/fact/query/PhabricatorFactDatapointQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
110
src/applications/fact/storage/PhabricatorFactDimension.php
Normal file
110
src/applications/fact/storage/PhabricatorFactDimension.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
|
|
|
@ -81,6 +81,7 @@ final class HarbormasterURIArtifact extends HarbormasterArtifact {
|
|||
array(
|
||||
'href' => $uri,
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer',
|
||||
),
|
||||
$name);
|
||||
}
|
||||
|
|
|
@ -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".
|
||||
|
|
|
@ -309,6 +309,8 @@ final class NuanceGitHubEventItemType
|
|||
'a',
|
||||
array(
|
||||
'href' => $event_uri,
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer',
|
||||
),
|
||||
$event_uri);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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')),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ final class PhabricatorPhurlLinkRemarkupRule extends PhutilRemarkupRule {
|
|||
array(
|
||||
'href' => $uri,
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer',
|
||||
),
|
||||
$name);
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
|
|
|
@ -99,7 +99,8 @@ final class PhabricatorLinkProfileMenuItem
|
|||
->setHref($href)
|
||||
->setName($name)
|
||||
->setIcon($icon_class)
|
||||
->setTooltip($tooltip);
|
||||
->setTooltip($tooltip)
|
||||
->setRel('noreferrer');
|
||||
|
||||
return array(
|
||||
$item,
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorFiletreeWidthSetting
|
||||
extends PhabricatorInternalSetting {
|
||||
|
||||
const SETTINGKEY = 'filetree.width';
|
||||
|
||||
public function getSettingName() {
|
||||
return pht('Filetree Width');
|
||||
}
|
||||
|
||||
}
|
|
@ -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 )---------------------------------------------------------- */
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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.'));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCustomFieldApplicationSearchDatasource
|
||||
extends PhabricatorTypeaheadProxyDatasource {
|
||||
|
||||
public function getComponentDatasources() {
|
||||
$datasources = parent::getComponentDatasources();
|
||||
|
||||
$datasources[] =
|
||||
new PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource();
|
||||
$datasources[] =
|
||||
new PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource();
|
||||
|
||||
return $datasources;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.'));
|
||||
}
|
||||
|
||||
}
|
|
@ -31,7 +31,11 @@ final class PhabricatorStandardCustomFieldLink
|
|||
|
||||
return phutil_tag(
|
||||
'a',
|
||||
array('href' => $value, 'target' => '_blank'),
|
||||
array(
|
||||
'href' => $value,
|
||||
'target' => '_blank',
|
||||
'rel' => 'noreferrer',
|
||||
),
|
||||
$value);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 )--------------------------------------- */
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
Loading…
Reference in a new issue