1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 02:02:41 +01:00

(stable) Promote 2016 Week 10

This commit is contained in:
epriestley 2016-03-04 17:24:53 -08:00
commit 2c67d9c8ac
145 changed files with 3717 additions and 2425 deletions

View file

@ -7,7 +7,7 @@
*/
return array(
'names' => array(
'core.pkg.css' => 'ecdca229',
'core.pkg.css' => 'dd1447be',
'core.pkg.js' => '7d8faf57',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => '2de124c9',
@ -25,7 +25,7 @@ return array(
'rsrc/css/aphront/notification.css' => '7f684b62',
'rsrc/css/aphront/panel-view.css' => '8427b78d',
'rsrc/css/aphront/phabricator-nav-view.css' => 'ac79a758',
'rsrc/css/aphront/table-view.css' => '6d01d468',
'rsrc/css/aphront/table-view.css' => '036b6cdc',
'rsrc/css/aphront/tokenizer.css' => '056da01b',
'rsrc/css/aphront/tooltip.css' => '1a07aea8',
'rsrc/css/aphront/typeahead-browse.css' => 'd8581d2c',
@ -52,7 +52,7 @@ return array(
'rsrc/css/application/conpherence/update.css' => 'faf6be09',
'rsrc/css/application/conpherence/widget-pane.css' => '775eaaba',
'rsrc/css/application/contentsource/content-source-view.css' => '4b8b05d4',
'rsrc/css/application/countdown/timer.css' => 'e7544472',
'rsrc/css/application/countdown/timer.css' => '96696f21',
'rsrc/css/application/daemon/bulk-job.css' => 'df9c1d4a',
'rsrc/css/application/dashboard/dashboard.css' => 'eb458607',
'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
@ -70,16 +70,16 @@ return array(
'rsrc/css/application/feed/feed.css' => 'ecd4ec57',
'rsrc/css/application/files/global-drag-and-drop.css' => '5c1b47c2',
'rsrc/css/application/flag/flag.css' => '5337623f',
'rsrc/css/application/harbormaster/harbormaster.css' => 'b0758ca5',
'rsrc/css/application/harbormaster/harbormaster.css' => '834879db',
'rsrc/css/application/herald/herald-test.css' => 'a52e323e',
'rsrc/css/application/herald/herald.css' => '826075fa',
'rsrc/css/application/herald/herald.css' => '46596280',
'rsrc/css/application/maniphest/batch-editor.css' => 'b0f0b6d5',
'rsrc/css/application/maniphest/report.css' => '9b9580b7',
'rsrc/css/application/maniphest/task-edit.css' => 'fda62a9b',
'rsrc/css/application/maniphest/task-summary.css' => '11cc5344',
'rsrc/css/application/objectselector/object-selector.css' => '85ee8ce6',
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
'rsrc/css/application/paste/paste.css' => 'a5157c48',
'rsrc/css/application/paste/paste.css' => '1898e534',
'rsrc/css/application/people/people-profile.css' => '2473d929',
'rsrc/css/application/phame/phame.css' => '737792d6',
'rsrc/css/application/pholio/pholio-edit.css' => '3ad9d1ee',
@ -92,18 +92,18 @@ return array(
'rsrc/css/application/policy/policy-edit.css' => '815c66f7',
'rsrc/css/application/policy/policy-transaction-detail.css' => '82100a43',
'rsrc/css/application/policy/policy.css' => '957ea14c',
'rsrc/css/application/ponder/ponder-view.css' => '212495e0',
'rsrc/css/application/ponder/ponder-view.css' => 'fbd45f96',
'rsrc/css/application/project/project-card-view.css' => '9418c97d',
'rsrc/css/application/project/project-view.css' => '298b7c5b',
'rsrc/css/application/project/project-view.css' => '9ce99f21',
'rsrc/css/application/releeph/releeph-core.css' => '9b3c5733',
'rsrc/css/application/releeph/releeph-preview-branch.css' => 'b7a6f4a5',
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '8d8b92cd',
'rsrc/css/application/releeph/releeph-request-typeahead.css' => '667a48ae',
'rsrc/css/application/search/search-results.css' => '7dea472c',
'rsrc/css/application/slowvote/slowvote.css' => 'da0afb1b',
'rsrc/css/application/slowvote/slowvote.css' => 'a94b7230',
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => '5b3563c8',
'rsrc/css/core/core.css' => 'd0801452',
'rsrc/css/core/remarkup.css' => 'fc228f08',
'rsrc/css/core/syntax.css' => '9fd11da8',
'rsrc/css/core/z-index.css' => '5b6fcf3f',
@ -123,8 +123,8 @@ return array(
'rsrc/css/phui/phui-action-panel.css' => '91c7b835',
'rsrc/css/phui/phui-badge.css' => 'f25c3476',
'rsrc/css/phui/phui-big-info-view.css' => 'bd903741',
'rsrc/css/phui/phui-box.css' => 'dd1294d3',
'rsrc/css/phui/phui-button.css' => 'edf464e9',
'rsrc/css/phui/phui-box.css' => 'c9e01148',
'rsrc/css/phui/phui-button.css' => 'a64a8de6',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-crumbs-view.css' => '79d536e5',
'rsrc/css/phui/phui-document-pro.css' => '92d5b648',
@ -134,7 +134,8 @@ return array(
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
'rsrc/css/phui/phui-form-view.css' => '4a1a0f5e',
'rsrc/css/phui/phui-form.css' => 'aac1d51d',
'rsrc/css/phui/phui-header-view.css' => 'a6d7b20d',
'rsrc/css/phui/phui-head-thing.css' => '11731da0',
'rsrc/css/phui/phui-header-view.css' => 'fc4acf14',
'rsrc/css/phui/phui-hovercard.css' => 'de1a2119',
'rsrc/css/phui/phui-icon-set-selector.css' => '1ab67aad',
'rsrc/css/phui/phui-icon.css' => '3f33ab57',
@ -142,7 +143,7 @@ return array(
'rsrc/css/phui/phui-info-panel.css' => '27ea50a1',
'rsrc/css/phui/phui-info-view.css' => '6d7c3509',
'rsrc/css/phui/phui-list.css' => '9da2aa00',
'rsrc/css/phui/phui-object-box.css' => '407eaf5a',
'rsrc/css/phui/phui-object-box.css' => '91628842',
'rsrc/css/phui/phui-object-item-list-view.css' => '18b2ce8e',
'rsrc/css/phui/phui-pager.css' => 'bea33d23',
'rsrc/css/phui/phui-pinboard-view.css' => '2495140e',
@ -151,10 +152,10 @@ return array(
'rsrc/css/phui/phui-remarkup-preview.css' => '1a8f2591',
'rsrc/css/phui/phui-segment-bar-view.css' => '46342871',
'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => '888cedb8',
'rsrc/css/phui/phui-status.css' => '37309046',
'rsrc/css/phui/phui-tag-view.css' => '9d5d4400',
'rsrc/css/phui/phui-timeline-view.css' => '2efceff8',
'rsrc/css/phui/phui-two-column-view.css' => 'a317616a',
'rsrc/css/phui/phui-two-column-view.css' => 'd0ad8c10',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
'rsrc/css/phui/workboards/phui-workcard.css' => '3646fb96',
@ -523,7 +524,7 @@ return array(
'aphront-list-filter-view-css' => '5d6f0526',
'aphront-multi-column-view-css' => 'fd18389d',
'aphront-panel-view-css' => '8427b78d',
'aphront-table-view-css' => '6d01d468',
'aphront-table-view-css' => '036b6cdc',
'aphront-tokenizer-control-css' => '056da01b',
'aphront-tooltip-css' => '1a07aea8',
'aphront-typeahead-control-css' => 'd4f16145',
@ -558,8 +559,8 @@ return array(
'font-fontawesome' => 'c43323c5',
'font-lato' => 'c7ccd872',
'global-drag-and-drop-css' => '5c1b47c2',
'harbormaster-css' => 'b0758ca5',
'herald-css' => '826075fa',
'harbormaster-css' => '834879db',
'herald-css' => '46596280',
'herald-rule-editor' => '746ca158',
'herald-test-css' => 'a52e323e',
'inline-comment-summary-css' => '51efda3a',
@ -740,7 +741,7 @@ return array(
'multirow-row-manager' => 'b5d57730',
'owners-path-editor' => 'aa1733d0',
'owners-path-editor-css' => '2f00933b',
'paste-css' => 'a5157c48',
'paste-css' => '1898e534',
'path-typeahead' => 'f7fc67ec',
'people-profile-css' => '2473d929',
'phabricator-action-list-view-css' => 'c5eba19d',
@ -748,8 +749,8 @@ return array(
'phabricator-busy' => '59a7976a',
'phabricator-chatlog-css' => 'd295b020',
'phabricator-content-source-view-css' => '4b8b05d4',
'phabricator-core-css' => '5b3563c8',
'phabricator-countdown-css' => 'e7544472',
'phabricator-core-css' => 'd0801452',
'phabricator-countdown-css' => '96696f21',
'phabricator-dashboard-css' => 'eb458607',
'phabricator-drag-and-drop-file-upload' => '81f182b5',
'phabricator-draggable-list' => '5a13c79f',
@ -772,7 +773,7 @@ return array(
'phabricator-search-results-css' => '7dea472c',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-side-menu-view-css' => '3a3d9f41',
'phabricator-slowvote-css' => 'da0afb1b',
'phabricator-slowvote-css' => 'a94b7230',
'phabricator-source-code-view-css' => 'cbeef983',
'phabricator-standard-page-view' => 'e709f6d0',
'phabricator-textareautils' => '5813016a',
@ -802,8 +803,8 @@ return array(
'phui-action-panel-css' => '91c7b835',
'phui-badge-view-css' => 'f25c3476',
'phui-big-info-view-css' => 'bd903741',
'phui-box-css' => 'dd1294d3',
'phui-button-css' => 'edf464e9',
'phui-box-css' => 'c9e01148',
'phui-button-css' => 'a64a8de6',
'phui-calendar-css' => 'ccabe893',
'phui-calendar-day-css' => 'd1cf6f93',
'phui-calendar-list-css' => 'c1c7f338',
@ -818,7 +819,8 @@ return array(
'phui-fontkit-css' => '9cda225e',
'phui-form-css' => 'aac1d51d',
'phui-form-view-css' => '4a1a0f5e',
'phui-header-view-css' => 'a6d7b20d',
'phui-head-thing-view-css' => '11731da0',
'phui-header-view-css' => 'fc4acf14',
'phui-hovercard' => '1bd28176',
'phui-hovercard-view-css' => 'de1a2119',
'phui-icon-set-selector-css' => '1ab67aad',
@ -828,7 +830,7 @@ return array(
'phui-info-view-css' => '6d7c3509',
'phui-inline-comment-view-css' => '0fdb3667',
'phui-list-view-css' => '9da2aa00',
'phui-object-box-css' => '407eaf5a',
'phui-object-box-css' => '91628842',
'phui-object-item-list-view-css' => '18b2ce8e',
'phui-pager-css' => 'bea33d23',
'phui-pinboard-view-css' => '2495140e',
@ -837,11 +839,11 @@ return array(
'phui-remarkup-preview-css' => '1a8f2591',
'phui-segment-bar-view-css' => '46342871',
'phui-spacing-css' => '042804d6',
'phui-status-list-view-css' => '888cedb8',
'phui-status-list-view-css' => '37309046',
'phui-tag-view-css' => '9d5d4400',
'phui-theme-css' => '027ba77e',
'phui-timeline-view-css' => '2efceff8',
'phui-two-column-view-css' => 'a317616a',
'phui-two-column-view-css' => 'd0ad8c10',
'phui-workboard-color-css' => 'ac6fe6a7',
'phui-workboard-view-css' => 'e6d89647',
'phui-workcard-view-css' => '3646fb96',
@ -855,9 +857,9 @@ return array(
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
'policy-transaction-detail-css' => '82100a43',
'ponder-view-css' => '212495e0',
'ponder-view-css' => 'fbd45f96',
'project-card-view-css' => '9418c97d',
'project-view-css' => '298b7c5b',
'project-view-css' => '9ce99f21',
'releeph-core' => '9b3c5733',
'releeph-preview-branch' => 'b7a6f4a5',
'releeph-request-differential-create-dialog' => '8d8b92cd',

View file

@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplanname_ngrams (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectID INT UNSIGNED NOT NULL,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
KEY `key_object` (objectID),
KEY `key_ngram` (ngram, objectID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,11 @@
<?php
$table = new HarbormasterBuildPlan();
foreach (new LiskMigrationIterator($table) as $plan) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$plan->getPHID(),
array(
'force' => true,
));
}

View file

@ -0,0 +1,7 @@
CREATE TABLE {$NAMESPACE}_drydock.drydock_blueprintname_ngrams (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
objectID INT UNSIGNED NOT NULL,
ngram CHAR(3) NOT NULL COLLATE {$COLLATE_TEXT},
KEY `key_object` (objectID),
KEY `key_ngram` (ngram, objectID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,11 @@
<?php
$table = new DrydockBlueprint();
foreach (new LiskMigrationIterator($table) as $blueprint) {
PhabricatorSearchWorker::queueDocumentForIndexing(
$blueprint->getPHID(),
array(
'force' => true,
));
}

View file

@ -0,0 +1,16 @@
CREATE TABLE {$NAMESPACE}_drydock.edge (
src VARBINARY(64) NOT NULL,
type INT UNSIGNED NOT NULL,
dst VARBINARY(64) NOT NULL,
dateCreated INT UNSIGNED NOT NULL,
seq INT UNSIGNED NOT NULL,
dataID INT UNSIGNED,
PRIMARY KEY (src, type, dst),
KEY `src` (src, type, dateCreated, seq),
UNIQUE KEY `key_dst` (dst, type, src)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
CREATE TABLE {$NAMESPACE}_drydock.edgedata (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -36,6 +36,7 @@ phutil_register_library_map(array(
'AlmanacDevice' => 'applications/almanac/storage/AlmanacDevice.php',
'AlmanacDeviceController' => 'applications/almanac/controller/AlmanacDeviceController.php',
'AlmanacDeviceEditController' => 'applications/almanac/controller/AlmanacDeviceEditController.php',
'AlmanacDeviceEditEngine' => 'applications/almanac/editor/AlmanacDeviceEditEngine.php',
'AlmanacDeviceEditor' => 'applications/almanac/editor/AlmanacDeviceEditor.php',
'AlmanacDeviceListController' => 'applications/almanac/controller/AlmanacDeviceListController.php',
'AlmanacDeviceNameNgrams' => 'applications/almanac/storage/AlmanacDeviceNameNgrams.php',
@ -106,6 +107,7 @@ phutil_register_library_map(array(
'AlmanacServiceController' => 'applications/almanac/controller/AlmanacServiceController.php',
'AlmanacServiceDatasource' => 'applications/almanac/typeahead/AlmanacServiceDatasource.php',
'AlmanacServiceEditController' => 'applications/almanac/controller/AlmanacServiceEditController.php',
'AlmanacServiceEditEngine' => 'applications/almanac/editor/AlmanacServiceEditEngine.php',
'AlmanacServiceEditor' => 'applications/almanac/editor/AlmanacServiceEditor.php',
'AlmanacServiceListController' => 'applications/almanac/controller/AlmanacServiceListController.php',
'AlmanacServiceNameNgrams' => 'applications/almanac/storage/AlmanacServiceNameNgrams.php',
@ -867,15 +869,16 @@ phutil_register_library_map(array(
'DrydockBlueprint' => 'applications/drydock/storage/DrydockBlueprint.php',
'DrydockBlueprintController' => 'applications/drydock/controller/DrydockBlueprintController.php',
'DrydockBlueprintCoreCustomField' => 'applications/drydock/customfield/DrydockBlueprintCoreCustomField.php',
'DrydockBlueprintCreateController' => 'applications/drydock/controller/DrydockBlueprintCreateController.php',
'DrydockBlueprintCustomField' => 'applications/drydock/customfield/DrydockBlueprintCustomField.php',
'DrydockBlueprintDatasource' => 'applications/drydock/typeahead/DrydockBlueprintDatasource.php',
'DrydockBlueprintDisableController' => 'applications/drydock/controller/DrydockBlueprintDisableController.php',
'DrydockBlueprintEditController' => 'applications/drydock/controller/DrydockBlueprintEditController.php',
'DrydockBlueprintEditEngine' => 'applications/drydock/editor/DrydockBlueprintEditEngine.php',
'DrydockBlueprintEditor' => 'applications/drydock/editor/DrydockBlueprintEditor.php',
'DrydockBlueprintImplementation' => 'applications/drydock/blueprint/DrydockBlueprintImplementation.php',
'DrydockBlueprintImplementationTestCase' => 'applications/drydock/blueprint/__tests__/DrydockBlueprintImplementationTestCase.php',
'DrydockBlueprintListController' => 'applications/drydock/controller/DrydockBlueprintListController.php',
'DrydockBlueprintNameNgrams' => 'applications/drydock/storage/DrydockBlueprintNameNgrams.php',
'DrydockBlueprintPHIDType' => 'applications/drydock/phid/DrydockBlueprintPHIDType.php',
'DrydockBlueprintQuery' => 'applications/drydock/query/DrydockBlueprintQuery.php',
'DrydockBlueprintSearchEngine' => 'applications/drydock/query/DrydockBlueprintSearchEngine.php',
@ -938,6 +941,7 @@ phutil_register_library_map(array(
'DrydockObjectAuthorizationView' => 'applications/drydock/view/DrydockObjectAuthorizationView.php',
'DrydockQuery' => 'applications/drydock/query/DrydockQuery.php',
'DrydockRepositoryOperation' => 'applications/drydock/storage/DrydockRepositoryOperation.php',
'DrydockRepositoryOperationController' => 'applications/drydock/controller/DrydockRepositoryOperationController.php',
'DrydockRepositoryOperationDismissController' => 'applications/drydock/controller/DrydockRepositoryOperationDismissController.php',
'DrydockRepositoryOperationListController' => 'applications/drydock/controller/DrydockRepositoryOperationListController.php',
'DrydockRepositoryOperationPHIDType' => 'applications/drydock/phid/DrydockRepositoryOperationPHIDType.php',
@ -965,6 +969,7 @@ phutil_register_library_map(array(
'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php',
'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php',
'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php',
'DrydockSchemaSpec' => 'applications/drydock/storage/DrydockSchemaSpec.php',
'DrydockSlotLock' => 'applications/drydock/storage/DrydockSlotLock.php',
'DrydockSlotLockException' => 'applications/drydock/exception/DrydockSlotLockException.php',
'DrydockSlotLockFailureLogType' => 'applications/drydock/logtype/DrydockSlotLockFailureLogType.php',
@ -1044,6 +1049,8 @@ phutil_register_library_map(array(
'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php',
'HarbormasterBuildLintMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildLintMessage.php',
'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php',
'HarbormasterBuildLogChunk' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php',
'HarbormasterBuildLogChunkIterator' => 'applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php',
'HarbormasterBuildLogPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildLogPHIDType.php',
'HarbormasterBuildLogQuery' => 'applications/harbormaster/query/HarbormasterBuildLogQuery.php',
'HarbormasterBuildMessage' => 'applications/harbormaster/storage/HarbormasterBuildMessage.php',
@ -1053,7 +1060,9 @@ phutil_register_library_map(array(
'HarbormasterBuildPlanDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildPlanDatasource.php',
'HarbormasterBuildPlanDefaultEditCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultEditCapability.php',
'HarbormasterBuildPlanDefaultViewCapability' => 'applications/harbormaster/capability/HarbormasterBuildPlanDefaultViewCapability.php',
'HarbormasterBuildPlanEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php',
'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
'HarbormasterBuildPlanNameNgrams' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php',
'HarbormasterBuildPlanPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPlanPHIDType.php',
'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php',
@ -1110,6 +1119,7 @@ phutil_register_library_map(array(
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
'HarbormasterLintPropertyView' => 'applications/harbormaster/view/HarbormasterLintPropertyView.php',
'HarbormasterManagementArchiveLogsWorkflow' => 'applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php',
'HarbormasterManagementBuildWorkflow' => 'applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php',
'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php',
'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php',
@ -1143,8 +1153,11 @@ phutil_register_library_map(array(
'HarbormasterThrowExceptionBuildStep' => 'applications/harbormaster/step/HarbormasterThrowExceptionBuildStep.php',
'HarbormasterUIEventListener' => 'applications/harbormaster/event/HarbormasterUIEventListener.php',
'HarbormasterURIArtifact' => 'applications/harbormaster/artifact/HarbormasterURIArtifact.php',
'HarbormasterUnitMessagesController' => 'applications/harbormaster/controller/HarbormasterUnitMessagesController.php',
'HarbormasterUnitMessageListController' => 'applications/harbormaster/controller/HarbormasterUnitMessageListController.php',
'HarbormasterUnitMessageViewController' => 'applications/harbormaster/controller/HarbormasterUnitMessageViewController.php',
'HarbormasterUnitPropertyView' => 'applications/harbormaster/view/HarbormasterUnitPropertyView.php',
'HarbormasterUnitStatus' => 'applications/harbormaster/constants/HarbormasterUnitStatus.php',
'HarbormasterUnitSummaryView' => 'applications/harbormaster/view/HarbormasterUnitSummaryView.php',
'HarbormasterUploadArtifactBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterUploadArtifactBuildStepImplementation.php',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php',
'HarbormasterWorker' => 'applications/harbormaster/worker/HarbormasterWorker.php',
@ -1519,6 +1532,7 @@ phutil_register_library_map(array(
'PHUIHandleListView' => 'applications/phid/view/PHUIHandleListView.php',
'PHUIHandleTagListView' => 'applications/phid/view/PHUIHandleTagListView.php',
'PHUIHandleView' => 'applications/phid/view/PHUIHandleView.php',
'PHUIHeadThingView' => 'view/phui/PHUIHeadThingView.php',
'PHUIHeaderView' => 'view/phui/PHUIHeaderView.php',
'PHUIHovercardUIExample' => 'applications/uiexample/examples/PHUIHovercardUIExample.php',
'PHUIHovercardView' => 'view/phui/PHUIHovercardView.php',
@ -3277,6 +3291,7 @@ phutil_register_library_map(array(
'PhabricatorStandardCustomFieldUsers' => 'infrastructure/customfield/standard/PhabricatorStandardCustomFieldUsers.php',
'PhabricatorStandardPageView' => 'view/page/PhabricatorStandardPageView.php',
'PhabricatorStandardSelectCustomFieldDatasource' => 'infrastructure/customfield/datasource/PhabricatorStandardSelectCustomFieldDatasource.php',
'PhabricatorStaticEditField' => 'applications/transactions/editfield/PhabricatorStaticEditField.php',
'PhabricatorStatusController' => 'applications/system/controller/PhabricatorStatusController.php',
'PhabricatorStatusUIExample' => 'applications/uiexample/examples/PhabricatorStatusUIExample.php',
'PhabricatorStorageFixtureScopeGuard' => 'infrastructure/testing/fixture/PhabricatorStorageFixtureScopeGuard.php',
@ -4031,6 +4046,7 @@ phutil_register_library_map(array(
),
'AlmanacDeviceController' => 'AlmanacController',
'AlmanacDeviceEditController' => 'AlmanacDeviceController',
'AlmanacDeviceEditEngine' => 'PhabricatorEditEngine',
'AlmanacDeviceEditor' => 'AlmanacEditor',
'AlmanacDeviceListController' => 'AlmanacDeviceController',
'AlmanacDeviceNameNgrams' => 'PhabricatorSearchNgrams',
@ -4132,6 +4148,7 @@ phutil_register_library_map(array(
'AlmanacServiceController' => 'AlmanacController',
'AlmanacServiceDatasource' => 'PhabricatorTypeaheadDatasource',
'AlmanacServiceEditController' => 'AlmanacServiceController',
'AlmanacServiceEditEngine' => 'PhabricatorEditEngine',
'AlmanacServiceEditor' => 'AlmanacEditor',
'AlmanacServiceListController' => 'AlmanacServiceController',
'AlmanacServiceNameNgrams' => 'PhabricatorSearchNgrams',
@ -4620,7 +4637,7 @@ phutil_register_library_map(array(
'DifferentialTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'DifferentialTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'DifferentialTransactionView' => 'PhabricatorApplicationTransactionView',
'DifferentialUnitField' => 'DifferentialHarbormasterField',
'DifferentialUnitField' => 'DifferentialCustomField',
'DifferentialUnitStatus' => 'Phobject',
'DifferentialUnitTestResult' => 'Phobject',
'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod',
@ -4963,21 +4980,24 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorCustomFieldInterface',
'PhabricatorNgramsInterface',
'PhabricatorProjectInterface',
),
'DrydockBlueprintController' => 'DrydockController',
'DrydockBlueprintCoreCustomField' => array(
'DrydockBlueprintCustomField',
'PhabricatorStandardCustomFieldInterface',
),
'DrydockBlueprintCreateController' => 'DrydockBlueprintController',
'DrydockBlueprintCustomField' => 'PhabricatorCustomField',
'DrydockBlueprintDatasource' => 'PhabricatorTypeaheadDatasource',
'DrydockBlueprintDisableController' => 'DrydockBlueprintController',
'DrydockBlueprintEditController' => 'DrydockBlueprintController',
'DrydockBlueprintEditEngine' => 'PhabricatorEditEngine',
'DrydockBlueprintEditor' => 'PhabricatorApplicationTransactionEditor',
'DrydockBlueprintImplementation' => 'Phobject',
'DrydockBlueprintImplementationTestCase' => 'PhabricatorTestCase',
'DrydockBlueprintListController' => 'DrydockBlueprintController',
'DrydockBlueprintNameNgrams' => 'PhabricatorSearchNgrams',
'DrydockBlueprintPHIDType' => 'PhabricatorPHIDType',
'DrydockBlueprintQuery' => 'DrydockQuery',
'DrydockBlueprintSearchEngine' => 'PhabricatorApplicationSearchEngine',
@ -5051,16 +5071,17 @@ phutil_register_library_map(array(
'DrydockDAO',
'PhabricatorPolicyInterface',
),
'DrydockRepositoryOperationDismissController' => 'DrydockController',
'DrydockRepositoryOperationListController' => 'DrydockController',
'DrydockRepositoryOperationController' => 'DrydockController',
'DrydockRepositoryOperationDismissController' => 'DrydockRepositoryOperationController',
'DrydockRepositoryOperationListController' => 'DrydockRepositoryOperationController',
'DrydockRepositoryOperationPHIDType' => 'PhabricatorPHIDType',
'DrydockRepositoryOperationQuery' => 'DrydockQuery',
'DrydockRepositoryOperationSearchEngine' => 'PhabricatorApplicationSearchEngine',
'DrydockRepositoryOperationStatusController' => 'DrydockController',
'DrydockRepositoryOperationStatusController' => 'DrydockRepositoryOperationController',
'DrydockRepositoryOperationStatusView' => 'AphrontView',
'DrydockRepositoryOperationType' => 'Phobject',
'DrydockRepositoryOperationUpdateWorker' => 'DrydockWorker',
'DrydockRepositoryOperationViewController' => 'DrydockController',
'DrydockRepositoryOperationViewController' => 'DrydockRepositoryOperationController',
'DrydockResource' => array(
'DrydockDAO',
'PhabricatorPolicyInterface',
@ -5081,6 +5102,7 @@ phutil_register_library_map(array(
'DrydockResourceViewController' => 'DrydockResourceController',
'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface',
'DrydockSSHCommandInterface' => 'DrydockCommandInterface',
'DrydockSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'DrydockSlotLock' => 'DrydockDAO',
'DrydockSlotLockException' => 'Exception',
'DrydockSlotLockFailureLogType' => 'DrydockLogType',
@ -5185,6 +5207,8 @@ phutil_register_library_map(array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
),
'HarbormasterBuildLogChunk' => 'HarbormasterDAO',
'HarbormasterBuildLogChunkIterator' => 'PhutilBufferedIterator',
'HarbormasterBuildLogPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildLogQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildMessage' => array(
@ -5198,11 +5222,15 @@ phutil_register_library_map(array(
'PhabricatorApplicationTransactionInterface',
'PhabricatorPolicyInterface',
'PhabricatorSubscribableInterface',
'PhabricatorNgramsInterface',
'PhabricatorProjectInterface',
),
'HarbormasterBuildPlanDatasource' => 'PhabricatorTypeaheadDatasource',
'HarbormasterBuildPlanDefaultEditCapability' => 'PhabricatorPolicyCapability',
'HarbormasterBuildPlanDefaultViewCapability' => 'PhabricatorPolicyCapability',
'HarbormasterBuildPlanEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildPlanNameNgrams' => 'PhabricatorSearchNgrams',
'HarbormasterBuildPlanPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine',
@ -5273,6 +5301,7 @@ phutil_register_library_map(array(
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLintMessagesController' => 'HarbormasterController',
'HarbormasterLintPropertyView' => 'AphrontView',
'HarbormasterManagementArchiveLogsWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementBuildWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow',
@ -5283,7 +5312,7 @@ phutil_register_library_map(array(
'HarbormasterPlanDisableController' => 'HarbormasterPlanController',
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
'HarbormasterPlanListController' => 'HarbormasterPlanController',
'HarbormasterPlanRunController' => 'HarbormasterController',
'HarbormasterPlanRunController' => 'HarbormasterPlanController',
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
'HarbormasterPrototypeBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterPublishFragmentBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
@ -5296,18 +5325,21 @@ phutil_register_library_map(array(
'HarbormasterScratchTable' => 'HarbormasterDAO',
'HarbormasterSendMessageConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterSleepBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterStepAddController' => 'HarbormasterController',
'HarbormasterStepDeleteController' => 'HarbormasterController',
'HarbormasterStepEditController' => 'HarbormasterController',
'HarbormasterStepViewController' => 'HarbormasterController',
'HarbormasterStepAddController' => 'HarbormasterPlanController',
'HarbormasterStepDeleteController' => 'HarbormasterPlanController',
'HarbormasterStepEditController' => 'HarbormasterPlanController',
'HarbormasterStepViewController' => 'HarbormasterPlanController',
'HarbormasterTargetEngine' => 'Phobject',
'HarbormasterTargetWorker' => 'HarbormasterWorker',
'HarbormasterTestBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterThrowExceptionBuildStep' => 'HarbormasterBuildStepImplementation',
'HarbormasterUIEventListener' => 'PhabricatorEventListener',
'HarbormasterURIArtifact' => 'HarbormasterArtifact',
'HarbormasterUnitMessagesController' => 'HarbormasterController',
'HarbormasterUnitMessageListController' => 'HarbormasterController',
'HarbormasterUnitMessageViewController' => 'HarbormasterController',
'HarbormasterUnitPropertyView' => 'AphrontView',
'HarbormasterUnitStatus' => 'Phobject',
'HarbormasterUnitSummaryView' => 'AphrontView',
'HarbormasterUploadArtifactBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWaitForPreviousBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterWorker' => 'PhabricatorWorker',
@ -5746,6 +5778,7 @@ phutil_register_library_map(array(
'PHUIHandleListView' => 'AphrontTagView',
'PHUIHandleTagListView' => 'AphrontTagView',
'PHUIHandleView' => 'AphrontView',
'PHUIHeadThingView' => 'AphrontTagView',
'PHUIHeaderView' => 'AphrontTagView',
'PHUIHovercardUIExample' => 'PhabricatorUIExample',
'PHUIHovercardView' => 'AphrontTagView',
@ -6103,7 +6136,7 @@ phutil_register_library_map(array(
'PhabricatorBadgesMailReceiver' => 'PhabricatorObjectMailReceiver',
'PhabricatorBadgesPHIDType' => 'PhabricatorPHIDType',
'PhabricatorBadgesQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorBadgesRecipientsListView' => 'AphrontTagView',
'PhabricatorBadgesRecipientsListView' => 'AphrontView',
'PhabricatorBadgesRemoveRecipientsController' => 'PhabricatorBadgesController',
'PhabricatorBadgesReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'PhabricatorBadgesSchemaSpec' => 'PhabricatorConfigSchemaSpec',
@ -6368,7 +6401,7 @@ phutil_register_library_map(array(
'PhabricatorCountdownTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorCountdownTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorCountdownTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorCountdownView' => 'AphrontTagView',
'PhabricatorCountdownView' => 'AphrontView',
'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
'PhabricatorCredentialsUsedByObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery',
@ -7811,6 +7844,7 @@ phutil_register_library_map(array(
'AphrontResponseProducerInterface',
),
'PhabricatorStandardSelectCustomFieldDatasource' => 'PhabricatorTypeaheadDatasource',
'PhabricatorStaticEditField' => 'PhabricatorEditField',
'PhabricatorStatusController' => 'PhabricatorController',
'PhabricatorStatusUIExample' => 'PhabricatorUIExample',
'PhabricatorStorageFixtureScopeGuard' => 'Phobject',

View file

@ -35,22 +35,18 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication {
);
}
public function isPrototype() {
return true;
}
public function getRoutes() {
return array(
'/almanac/' => array(
'' => 'AlmanacConsoleController',
'(?P<objectType>service)/' => array(
$this->getQueryRoutePattern() => 'AlmanacServiceListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacServiceEditController',
$this->getEditRoutePattern('edit/') => 'AlmanacServiceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacServiceViewController',
),
'(?P<objectType>device)/' => array(
$this->getQueryRoutePattern() => 'AlmanacDeviceListController',
'edit/(?:(?P<id>\d+)/)?' => 'AlmanacDeviceEditController',
$this->getEditRoutePattern('edit/') => 'AlmanacDeviceEditController',
'view/(?P<name>[^/]+)/' => 'AlmanacDeviceViewController',
),
'interface/' => array(

View file

@ -158,16 +158,16 @@ abstract class AlmanacController
->setIcon('fa-plus');
$header = id(new PHUIHeaderView())
->setHeader(pht('Properties'))
->setHeader(pht('PROPERTIES'))
->addActionLink($add_button);
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
protected function addClusterMessage(
PHUIObjectBoxView $box,
$positive,
$negative) {
@ -194,14 +194,13 @@ abstract class AlmanacController
$icon = id(new PHUIIconView())
->setIcon('fa-sitemap');
$error_view = id(new PHUIInfoView())
return id(new PHUIInfoView())
->setSeverity($severity)
->setErrors(
array(
array($icon, ' ', $message, ' ', $doc_link),
));
$box->setInfoView($error_view);
}
protected function getPropertyDeleteURI($object) {

View file

@ -2,6 +2,11 @@
abstract class AlmanacDeviceController extends AlmanacController {
public function buildApplicationMenu() {
return $this->newApplicationMenu()
->setSearchEngine(new AlmanacDeviceSearchEngine());
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();

View file

@ -4,160 +4,9 @@ final class AlmanacDeviceEditController
extends AlmanacDeviceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$list_uri = $this->getApplicationURI('device/');
$id = $request->getURIData('id');
if ($id) {
$device = id(new AlmanacDeviceQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$device) {
return new Aphront404Response();
}
$is_new = false;
$device_uri = $device->getURI();
$cancel_uri = $device_uri;
$title = pht('Edit Device');
$save_button = pht('Save Changes');
} else {
$this->requireApplicationCapability(
AlmanacCreateDevicesCapability::CAPABILITY);
$device = AlmanacDevice::initializeNewDevice();
$is_new = true;
$cancel_uri = $list_uri;
$title = pht('Create Device');
$save_button = pht('Create Device');
}
$v_name = $device->getName();
$e_name = true;
$validation_exception = null;
if ($is_new) {
$v_projects = array();
} else {
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$device->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$v_projects = array_reverse($v_projects);
}
if ($request->isFormPost()) {
$v_name = $request->getStr('name');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$v_projects = $request->getArr('projects');
$type_name = AlmanacDeviceTransaction::TYPE_NAME;
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
$xactions = array();
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType($type_name)
->setNewValue($v_name);
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType($type_view)
->setNewValue($v_view);
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType($type_edit)
->setNewValue($v_edit);
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new AlmanacDeviceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $proj_edge_type)
->setNewValue(array('=' => array_fuse($v_projects)));
$editor = id(new AlmanacDeviceEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($device, $xactions);
$device_uri = $device->getURI();
return id(new AphrontRedirectResponse())->setURI($device_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_name = $ex->getShortMessage($type_name);
$device->setViewPolicy($v_view);
$device->setEditPolicy($v_edit);
}
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($device)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($device)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($device)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setValue($v_projects)
->setDatasource(new PhabricatorProjectDatasource()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($save_button));
$box = id(new PHUIObjectBoxView())
->setValidationException($validation_exception)
->setHeaderText($title)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
if ($is_new) {
$crumbs->addTextCrumb(pht('Create Device'));
} else {
$crumbs->addTextCrumb($device->getName(), $device_uri);
$crumbs->addTextCrumb(pht('Edit'));
}
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$box,
));
return id(new AlmanacDeviceEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -8,45 +8,19 @@ final class AlmanacDeviceListController
}
public function handleRequest(AphrontRequest $request) {
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine(new AlmanacDeviceSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
return id(new AlmanacDeviceSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$can_create = $this->hasApplicationCapability(
AlmanacCreateDevicesCapability::CAPABILITY);
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Device'))
->setHref($this->getApplicationURI('device/edit/'))
->setIcon('fa-plus-square')
->setDisabled(!$can_create)
->setWorkflow(!$can_create));
id(new AlmanacDeviceEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
public function buildSideNavView() {
$viewer = $this->getViewer();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new AlmanacDeviceSearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
}

View file

@ -23,22 +23,18 @@ final class AlmanacDeviceViewController
$title = pht('Device %s', $device->getName());
$property_list = $this->buildPropertyList($device);
$action_list = $this->buildActionList($device);
$property_list->setActionList($action_list);
$properties = $this->buildPropertyList($device);
$actions = $this->buildActionList($device);
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($device->getName())
->setPolicyObject($device);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($property_list);
->setPolicyObject($device)
->setHeaderIcon('fa-server');
$issue = null;
if ($device->isClusterDevice()) {
$this->addClusterMessage(
$box,
$issue = $this->addClusterMessage(
pht('This device is bound to a cluster service.'),
pht(
'This device is bound to a cluster service. You do not have '.
@ -50,23 +46,32 @@ final class AlmanacDeviceViewController
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($device->getName());
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$device,
new AlmanacDeviceTransactionQuery());
$timeline->setShouldTerminate(true);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$box,
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(array(
$issue,
$interfaces,
$this->buildAlmanacPropertiesTable($device),
$this->buildSSHKeysTable($device),
$this->buildServicesTable($device),
$timeline,
))
->setPropertyList($properties)
->setActionList($actions);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$view,
));
}
@ -123,7 +128,7 @@ final class AlmanacDeviceViewController
->setCanEdit($can_edit);
$header = id(new PHUIHeaderView())
->setHeader(pht('Device Interfaces'))
->setHeader(pht('DEVICE INTERFACES'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
@ -135,6 +140,7 @@ final class AlmanacDeviceViewController
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
@ -172,7 +178,7 @@ final class AlmanacDeviceViewController
$upload_uri = '/auth/sshkey/upload/?objectPHID='.$device_phid;
$header = id(new PHUIHeaderView())
->setHeader(pht('SSH Public Keys'))
->setHeader(pht('SSH PUBLIC KEYS'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
@ -196,9 +202,8 @@ final class AlmanacDeviceViewController
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}
private function buildServicesTable(AlmanacDevice $device) {
@ -244,7 +249,8 @@ final class AlmanacDeviceViewController
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Bound Services'))
->setHeaderText(pht('BOUND SERVICES'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}

View file

@ -21,34 +21,38 @@ final class AlmanacNamespaceViewController
$title = pht('Namespace %s', $namespace->getName());
$property_list = $this->buildPropertyList($namespace);
$action_list = $this->buildActionList($namespace);
$property_list->setActionList($action_list);
$properties = $this->buildPropertyList($namespace);
$actions = $this->buildActionList($namespace);
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($namespace->getName())
->setPolicyObject($namespace);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($property_list);
->setPolicyObject($namespace)
->setHeaderIcon('fa-asterisk');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($namespace->getName());
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$namespace,
new AlmanacNamespaceTransactionQuery());
$timeline->setShouldTerminate(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(array(
$timeline,
))
->setPropertyList($properties)
->setActionList($actions);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$box,
$timeline,
$view,
));
}
@ -56,7 +60,10 @@ final class AlmanacNamespaceViewController
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
->setUser($viewer)
->setObject($namespace);
$properties->invokeWillRenderEvent();
return $properties;
}

View file

@ -21,34 +21,38 @@ final class AlmanacNetworkViewController
$title = pht('Network %s', $network->getName());
$property_list = $this->buildPropertyList($network);
$action_list = $this->buildActionList($network);
$property_list->setActionList($action_list);
$properties = $this->buildPropertyList($network);
$actions = $this->buildActionList($network);
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($network->getName())
->setHeaderIcon('fa-globe')
->setPolicyObject($network);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($property_list);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($network->getName());
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$network,
new AlmanacNetworkTransactionQuery());
$timeline->setShouldTerminate(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(array(
$timeline,
))
->setPropertyList($properties)
->setActionList($actions);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$box,
$timeline,
$view,
));
}
@ -56,7 +60,10 @@ final class AlmanacNetworkViewController
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
->setUser($viewer)
->setObject($network);
$properties->invokeWillRenderEvent();
return $properties;
}

View file

@ -11,6 +11,11 @@ abstract class AlmanacServiceController extends AlmanacController {
return $crumbs;
}
public function buildApplicationMenu() {
return $this->newApplicationMenu()
->setSearchEngine(new AlmanacServiceSearchEngine());
}
protected function getPropertyDeleteURI($object) {
$id = $object->getID();
return "/almanac/service/delete/{$id}/";

View file

@ -4,175 +4,28 @@ final class AlmanacServiceEditController
extends AlmanacServiceController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$list_uri = $this->getApplicationURI('service/');
$engine = id(new AlmanacServiceEditEngine())
->setController($this);
$id = $request->getURIData('id');
if ($id) {
$service = id(new AlmanacServiceQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$service) {
return new Aphront404Response();
}
$is_new = false;
$service_uri = $service->getURI();
$cancel_uri = $service_uri;
$title = pht('Edit Service');
$save_button = pht('Save Changes');
} else {
$cancel_uri = $list_uri;
if (!$id) {
$this->requireApplicationCapability(
AlmanacCreateServicesCapability::CAPABILITY);
$list_uri = $this->getApplicationURI('service/');
$service_type = $request->getStr('serviceType');
try {
$service = AlmanacService::initializeNewService($service_type);
} catch (Exception $ex) {
return $this->buildServiceTypeResponse($cancel_uri);
$service_types = AlmanacServiceType::getAllServiceTypes();
if (empty($service_types[$service_type])) {
return $this->buildServiceTypeResponse($list_uri);
}
if ($service->isClusterService()) {
$this->requireApplicationCapability(
AlmanacManageClusterServicesCapability::CAPABILITY);
$engine
->addContextParameter('serviceType', $service_type)
->setServiceType($service_type);
}
$is_new = true;
$title = pht('Create Service');
$save_button = pht('Create Service');
}
$v_name = $service->getName();
$e_name = true;
$validation_exception = null;
if ($is_new) {
$v_projects = array();
} else {
$v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs(
$service->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
$v_projects = array_reverse($v_projects);
}
if ($request->isFormPost() && $request->getStr('edit')) {
$v_name = $request->getStr('name');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$v_projects = $request->getArr('projects');
$type_name = AlmanacServiceTransaction::TYPE_NAME;
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
$xactions = array();
$xactions[] = id(new AlmanacServiceTransaction())
->setTransactionType($type_name)
->setNewValue($v_name);
$xactions[] = id(new AlmanacServiceTransaction())
->setTransactionType($type_view)
->setNewValue($v_view);
$xactions[] = id(new AlmanacServiceTransaction())
->setTransactionType($type_edit)
->setNewValue($v_edit);
$proj_edge_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST;
$xactions[] = id(new AlmanacServiceTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue('edge:type', $proj_edge_type)
->setNewValue(array('=' => array_fuse($v_projects)));
$editor = id(new AlmanacServiceEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($service, $xactions);
$service_uri = $service->getURI();
return id(new AphrontRedirectResponse())->setURI($service_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_name = $ex->getShortMessage($type_name);
$service->setViewPolicy($v_view);
$service->setEditPolicy($v_edit);
}
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($service)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('edit', true)
->addHiddenInput('serviceType', $service->getServiceType())
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($service)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($service)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setValue($v_projects)
->setDatasource(new PhabricatorProjectDatasource()))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($cancel_uri)
->setValue($save_button));
$box = id(new PHUIObjectBoxView())
->setValidationException($validation_exception)
->setHeaderText($title)
->appendChild($form);
$crumbs = $this->buildApplicationCrumbs();
if ($is_new) {
$crumbs->addTextCrumb(pht('Create Service'));
} else {
$crumbs->addTextCrumb($service->getName(), $service_uri);
$crumbs->addTextCrumb(pht('Edit'));
}
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$box,
));
return $engine->buildResponse();
}
private function buildServiceTypeResponse($cancel_uri) {
@ -194,7 +47,6 @@ final class AlmanacServiceEditController
pht('You have permission to create cluster services.'),
pht('You do not have permission to create new cluster services.'));
$type_control = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Service Type'))
->setName('serviceType')
@ -239,14 +91,10 @@ final class AlmanacServiceEditController
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($box);
}
}

View file

@ -8,45 +8,19 @@ final class AlmanacServiceListController
}
public function handleRequest(AphrontRequest $request) {
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine(new AlmanacServiceSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
return id(new AlmanacServiceSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$can_create = $this->hasApplicationCapability(
AlmanacCreateServicesCapability::CAPABILITY);
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('Create Service'))
->setHref($this->getApplicationURI('service/edit/'))
->setIcon('fa-plus-square')
->setDisabled(!$can_create)
->setWorkflow(!$can_create));
id(new AlmanacServiceEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
public function buildSideNavView() {
$viewer = $this->getViewer();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new AlmanacServiceSearchEngine())
->setViewer($viewer)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
}

View file

@ -23,22 +23,19 @@ final class AlmanacServiceViewController
$title = pht('Service %s', $service->getName());
$property_list = $this->buildPropertyList($service);
$action_list = $this->buildActionList($service);
$property_list->setActionList($action_list);
$properties = $this->buildPropertyList($service);
$actions = $this->buildActionList($service);
$details = $this->buildPropertySection($service);
$header = id(new PHUIHeaderView())
->setUser($viewer)
->setHeader($service->getName())
->setPolicyObject($service);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($property_list);
->setPolicyObject($service)
->setHeaderIcon('fa-plug');
$issue = null;
if ($service->isClusterService()) {
$this->addClusterMessage(
$box,
$issue = $this->addClusterMessage(
pht('This is a cluster service.'),
pht(
'This service is a cluster service. You do not have permission to '.
@ -49,36 +46,62 @@ final class AlmanacServiceViewController
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($service->getName());
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$service,
new AlmanacServiceTransactionQuery());
$timeline->setShouldTerminate(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(array(
$issue,
$details,
$bindings,
$this->buildAlmanacPropertiesTable($service),
$timeline,
))
->setPropertyList($properties)
->setActionList($actions);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$box,
$bindings,
$this->buildAlmanacPropertiesTable($service),
$timeline,
$view,
));
}
private function buildPropertyList(AlmanacService $service) {
private function buildPropertyList(
AlmanacService $service) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($service);
$view->invokeWillRenderEvent();
return $view;
}
private function buildPropertySection(
AlmanacService $service) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($service);
->setUser($viewer);
$properties->addProperty(
pht('Service Type'),
$service->getServiceImplementation()->getServiceTypeShortName());
return $properties;
return id(new PHUIObjectBoxView())
->setHeaderText(pht('DETAILS'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($properties);
}
private function buildActionList(AlmanacService $service) {
@ -126,7 +149,7 @@ final class AlmanacServiceViewController
->setHideServiceColumn(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Service Bindings'))
->setHeader(pht('SERVICE BINDINGS'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
@ -138,6 +161,7 @@ final class AlmanacServiceViewController
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setTable($table);
}

View file

@ -0,0 +1,85 @@
<?php
final class AlmanacDeviceEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'almanac.device';
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Almanac Devices');
}
public function getSummaryHeader() {
return pht('Edit Almanac Device Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Almanac devices.');
}
public function getEngineApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
protected function newEditableObject() {
return AlmanacDevice::initializeNewDevice();
}
protected function newObjectQuery() {
return new AlmanacDeviceQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Device');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Device');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Device: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Device');
}
protected function getObjectCreateShortText() {
return pht('Create Device');
}
protected function getEditorURI() {
return '/almanac/device/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/almanac/device/';
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
AlmanacCreateDevicesCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the device.'))
->setTransactionType(AlmanacDeviceTransaction::TYPE_NAME)
->setIsRequired(true)
->setValue($object->getName()),
);
}
}

View file

@ -0,0 +1,97 @@
<?php
final class AlmanacServiceEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'almanac.service';
private $serviceType;
public function setServiceType($service_type) {
$this->serviceType = $service_type;
return $this;
}
public function getServiceType() {
return $this->serviceType;
}
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Almanac Services');
}
public function getSummaryHeader() {
return pht('Edit Almanac Service Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Almanac services.');
}
public function getEngineApplicationClass() {
return 'PhabricatorAlmanacApplication';
}
protected function newEditableObject() {
$service_type = $this->getServiceType();
return AlmanacService::initializeNewService($service_type);
}
protected function newObjectQuery() {
return new AlmanacServiceQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Service');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Service');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Service: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Service');
}
protected function getObjectCreateShortText() {
return pht('Create Service');
}
protected function getEditorURI() {
return '/almanac/service/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/almanac/service/';
}
protected function getObjectViewURI($object) {
return $object->getURI();
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
AlmanacCreateServicesCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the service.'))
->setTransactionType(AlmanacServiceTransaction::TYPE_NAME)
->setIsRequired(true)
->setValue($object->getName()),
);
}
}

View file

@ -152,4 +152,28 @@ final class AlmanacServiceEditor
return $errors;
}
protected function validateAllTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
$errors = parent::validateAllTransactions($object, $xactions);
if ($object->isClusterService()) {
$can_manage = PhabricatorPolicyFilter::hasCapability(
$this->getActor(),
new PhabricatorAlmanacApplication(),
AlmanacManageClusterServicesCapability::CAPABILITY);
if (!$can_manage) {
$errors[] = new PhabricatorApplicationTransactionValidationError(
null,
pht('Restricted'),
pht('You do not have permission to manage cluster services.'),
null);
}
}
return $errors;
}
}

View file

@ -22,6 +22,7 @@ final class PhabricatorBadgesEditRecipientsController
}
$recipient_phids = $badge->getRecipientPHIDs();
$view_uri = $this->getApplicationURI('view/'.$badge->getID().'/');
if ($request->isFormPost()) {
$recipient_spec = array();
@ -53,7 +54,7 @@ final class PhabricatorBadgesEditRecipientsController
->applyTransactions($badge, $xactions);
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI());
->setURI($view_uri);
}
$recipient_phids = array_reverse($recipient_phids);
@ -76,44 +77,26 @@ final class PhabricatorBadgesEditRecipientsController
$title = pht('Add Recipient');
if ($can_edit) {
$header_name = pht('Edit Recipients');
$view_uri = $this->getApplicationURI('view/'.$badge->getID().'/');
$form = new AphrontFormView();
$form
->setUser($viewer)
->setFullWidth(true)
->appendControl(
id(new AphrontFormTokenizerControl())
->setName('phids')
->setLabel(pht('Add Recipients'))
->setDatasource(new PhabricatorPeopleDatasource()))
->appendChild(
id(new AphrontFormSubmitControl())
->setDatasource(new PhabricatorPeopleDatasource()));
}
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Award Badges'))
->appendForm($form)
->addCancelButton($view_uri)
->setValue(pht('Add Recipients')));
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setForm($form);
}
->addSubmitButton(pht('Add Recipients'));
$recipient_list = id(new PhabricatorBadgesRecipientsListView())
->setBadge($badge)
->setHandles($handles)
->setUser($viewer);
$badge_url = $this->getApplicationURI('view/'.$id.'/');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($badge->getName(), $badge_url);
$crumbs->addTextCrumb(pht('Recipients'));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$form_box,
$recipient_list,
));
return $dialog;
}
}

View file

@ -28,8 +28,7 @@ final class PhabricatorBadgesRemoveRecipientsController
return new Aphront404Response();
}
$recipients_uri =
$this->getApplicationURI('recipients/'.$badge->getID().'/');
$view_uri = $this->getApplicationURI('view/'.$badge->getID().'/');
if ($request->isFormPost()) {
$recipient_spec = array();
@ -52,7 +51,7 @@ final class PhabricatorBadgesRemoveRecipientsController
->applyTransactions($badge, $xactions);
return id(new AphrontRedirectResponse())
->setURI($recipients_uri);
->setURI($view_uri);
}
$handle = id(new PhabricatorHandleQuery())
@ -68,7 +67,7 @@ final class PhabricatorBadgesRemoveRecipientsController
'Really revoke the badge "%s" from %s?',
phutil_tag('strong', array(), $badge->getName()),
phutil_tag('strong', array(), $handle->getName())))
->addCancelButton($recipients_uri)
->addCancelButton($view_uri)
->addSubmitButton(pht('Revoke Badge'));
return $dialog;

View file

@ -22,6 +22,7 @@ final class PhabricatorBadgesViewController
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($badge->getName());
$crumbs->setBorder(true);
$title = $badge->getName();
if ($badge->isArchived()) {
@ -39,15 +40,12 @@ final class PhabricatorBadgesViewController
->setHeader($badge->getName())
->setUser($viewer)
->setPolicyObject($badge)
->setStatus($status_icon, $status_color, $status_name);
->setStatus($status_icon, $status_color, $status_name)
->setHeaderIcon('fa-trophy');
$properties = $this->buildPropertyListView($badge);
$actions = $this->buildActionListView($badge);
$properties->setActionList($actions);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$details = $this->buildDetailsView($badge);
$timeline = $this->buildTransactionTimeline(
$badge,
@ -64,26 +62,47 @@ final class PhabricatorBadgesViewController
$add_comment = $this->buildCommentForm($badge);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn(array(
$recipient_list,
$timeline,
$add_comment,
))
->setPropertyList($properties)
->setActionList($actions)
->addPropertySection(pht('BADGE DETAILS'), $details);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($badge->getPHID()))
->appendChild(
array(
$box,
$recipient_list,
$timeline,
$add_comment,
$view,
));
}
private function buildPropertyListView(PhabricatorBadgesBadge $badge) {
private function buildPropertyListView(
PhabricatorBadgesBadge $badge) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($badge);
$view->invokeWillRenderEvent();
return $view;
}
private function buildDetailsView(
PhabricatorBadgesBadge $badge) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$quality = idx($badge->getQualityNameMap(), $badge->getQuality());
$view->addProperty(
@ -99,8 +118,6 @@ final class PhabricatorBadgesViewController
pht('Flavor'),
$badge->getFlavor());
$view->invokeWillRenderEvent();
$description = $badge->getDescription();
if (strlen($description)) {
$view->addSectionHeader(
@ -160,9 +177,10 @@ final class PhabricatorBadgesViewController
$view->addAction(
id(new PhabricatorActionView())
->setName('Manage Recipients')
->setName('Add Recipients')
->setIcon('fa-users')
->setDisabled(!$can_edit)
->setWorkflow(true)
->setHref($this->getApplicationURI("/recipients/{$id}/")));
return $view;

View file

@ -1,6 +1,6 @@
<?php
final class PhabricatorBadgesRecipientsListView extends AphrontTagView {
final class PhabricatorBadgesRecipientsListView extends AphrontView {
private $badge;
private $handles;
@ -15,7 +15,7 @@ final class PhabricatorBadgesRecipientsListView extends AphrontTagView {
return $this;
}
protected function getTagContent() {
public function render() {
$viewer = $this->user;

View file

@ -52,7 +52,8 @@ final class PhabricatorCalendarEventViewController
$title = 'E'.$event->getID();
$page_title = $title.' '.$event->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($title, '/E'.$event->getID());
$crumbs->addTextCrumb($title);
$crumbs->setBorder(true);
}
if (!$event->getIsGhostEvent()) {
@ -63,12 +64,9 @@ final class PhabricatorCalendarEventViewController
$header = $this->buildHeaderView($event);
$actions = $this->buildActionView($event);
$properties = $this->buildPropertyView($event);
$properties->setActionList($actions);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$properties = $this->buildPropertyListView($event);
$details = $this->buildPropertySection($event);
$description = $this->buildDescriptionView($event);
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$add_comment_header = $is_serious
@ -90,26 +88,32 @@ final class PhabricatorCalendarEventViewController
->setAction($comment_uri)
->setSubmitButtonName(pht('Add Comment'));
return $this->buildApplicationPage(
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn($timeline)
->setPropertyList($properties)
->addPropertySection(pht('DETAILS'), $details)
->addPropertySection(pht('DESCRIPTION'), $description)
->setActionList($actions);
return $this->newPage()
->setTitle($page_title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($event->getPHID()))
->appendChild(
array(
$crumbs,
$box,
$timeline,
$add_comment_form,
),
array(
'title' => $page_title,
'pageObjects' => array($event->getPHID()),
$view,
));
}
private function buildHeaderView(PhabricatorCalendarEvent $event) {
$viewer = $this->getRequest()->getUser();
private function buildHeaderView(
PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
$id = $event->getID();
$is_cancelled = $event->getIsCancelled();
$icon = $is_cancelled ? ('fa-times') : ('fa-calendar');
$color = $is_cancelled ? ('grey') : ('green');
$icon = $is_cancelled ? ('fa-ban') : ('fa-check');
$color = $is_cancelled ? ('red') : ('bluegrey');
$status = $is_cancelled ? pht('Cancelled') : pht('Active');
$invite_status = $event->getUserInviteStatus($viewer->getPHID());
@ -120,7 +124,8 @@ final class PhabricatorCalendarEventViewController
->setUser($viewer)
->setHeader($event->getName())
->setStatus($icon, $color, $status)
->setPolicyObject($event);
->setPolicyObject($event)
->setHeaderIcon('fa-calendar');
if ($is_invite_pending) {
$decline_button = id(new PHUIButtonView())
@ -245,13 +250,26 @@ final class PhabricatorCalendarEventViewController
return $actions;
}
private function buildPropertyView(PhabricatorCalendarEvent $event) {
$viewer = $this->getRequest()->getUser();
private function buildPropertyListView(
PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($event);
$properties->invokeWillRenderEvent();
return $properties;
}
private function buildPropertySection(
PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
if ($event->getIsAllDay()) {
$date_start = phabricator_date($event->getDateFrom(), $viewer);
$date_end = phabricator_date($event->getDateTo(), $viewer);
@ -362,16 +380,23 @@ final class PhabricatorCalendarEventViewController
id(new PhabricatorCalendarIconSet())
->getIconLabel($event->getIcon()));
if (strlen($event->getDescription())) {
$description = new PHUIRemarkupView($viewer, $event->getDescription());
$properties->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$properties->addTextContent($description);
}
return $properties;
}
private function buildDescriptionView(
PhabricatorCalendarEvent $event) {
$viewer = $this->getViewer();
$properties = id(new PHUIPropertyListView())
->setUser($viewer);
if (strlen($event->getDescription())) {
$description = new PHUIRemarkupView($viewer, $event->getDescription());
$properties->addTextContent($description);
return $properties;
}
return null;
}
}

View file

@ -192,10 +192,7 @@ final class CelerityDefaultPostprocessor
'sh-disabledbackground' => '#f3f3f3',
// Background color for "most" themes.
'page.background' => '#f1f1f4',
// Background color for "dark" themes.
'page.background.dark' => '#ebecee',
'page.background' => '#f8f8fb',
'menu.profile.text' => 'rgba(255,255,255,.8)',
'menu.profile.text.selected' => 'rgba(255,255,255,1)',

View file

@ -21,15 +21,15 @@ final class PhabricatorCountdownViewController
$countdown_view = id(new PhabricatorCountdownView())
->setUser($viewer)
->setCountdown($countdown)
->setHeadless(true);
->setCountdown($countdown);
$id = $countdown->getID();
$title = $countdown->getTitle();
$crumbs = $this
->buildApplicationCrumbs()
->addTextCrumb("C{$id}");
->addTextCrumb("C{$id}")
->setBorder(true);
$epoch = $countdown->getEpoch();
if ($epoch >= PhabricatorTime::getNow()) {
@ -46,22 +46,30 @@ final class PhabricatorCountdownViewController
->setHeader($title)
->setUser($viewer)
->setPolicyObject($countdown)
->setStatus($icon, $color, $status);
->setStatus($icon, $color, $status)
->setHeaderIcon('fa-rocket');
$actions = $this->buildActionListView($countdown);
$properties = $this->buildPropertyListView($countdown, $actions);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$properties = $this->buildPropertyListView($countdown);
$subheader = $this->buildSubheaderView($countdown);
$timeline = $this->buildTransactionTimeline(
$countdown,
new PhabricatorCountdownTransactionQuery());
$add_comment = $this->buildCommentForm($countdown);
$content = array(
$countdown_view,
$timeline,
$add_comment,
);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setSubheader($subheader)
->setMainColumn($content)
->setPropertyList($properties)
->setActionList($actions);
return $this->newPage()
->setTitle($title)
@ -72,10 +80,7 @@ final class PhabricatorCountdownViewController
))
->appendChild(
array(
$object_box,
$countdown_view,
$timeline,
$add_comment,
$view,
));
}
@ -114,32 +119,38 @@ final class PhabricatorCountdownViewController
}
private function buildPropertyListView(
PhabricatorCountdown $countdown,
PhabricatorActionListView $actions) {
PhabricatorCountdown $countdown) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($countdown)
->setActionList($actions);
$view->addProperty(
pht('Author'),
$viewer->renderHandle($countdown->getAuthorPHID()));
->setObject($countdown);
$view->invokeWillRenderEvent();
$description = $countdown->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($viewer, $description);
$view->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$view->addTextContent($description);
return $view;
}
return $view;
private function buildSubheaderView(
PhabricatorCountdown $countdown) {
$viewer = $this->getViewer();
$author = $viewer->renderHandle($countdown->getAuthorPHID())->render();
$date = phabricator_datetime($countdown->getDateCreated(), $viewer);
$author = phutil_tag('strong', array(), $author);
$person = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withPHIDs(array($countdown->getAuthorPHID()))
->needProfileImage(true)
->executeOne();
$image_uri = $person->getProfileImageURI();
$image_href = '/p/'.$person->getUsername();
$content = pht('Authored by %s on %s.', $author, $date);
return id(new PHUIHeadThingView())
->setImage($image_uri)
->setImageHref($image_href)
->setContent($content);
}
private function buildCommentForm(PhabricatorCountdown $countdown) {

View file

@ -1,35 +1,19 @@
<?php
final class PhabricatorCountdownView extends AphrontTagView {
final class PhabricatorCountdownView extends AphrontView {
private $countdown;
private $headless;
public function setHeadless($headless) {
$this->headless = $headless;
return $this;
}
public function setCountdown(PhabricatorCountdown $countdown) {
$this->countdown = $countdown;
return $this;
}
protected function getTagContent() {
public function render() {
$countdown = $this->countdown;
require_celerity_resource('phabricator-countdown-css');
$header = null;
if (!$this->headless) {
$header = phutil_tag(
'div',
array(
'class' => 'phabricator-timer-header',
),
array(
$header_text = array(
'C'.$countdown->getID(),
' ',
phutil_tag(
@ -38,9 +22,10 @@ final class PhabricatorCountdownView extends AphrontTagView {
'href' => '/countdown/'.$countdown->getID(),
),
$countdown->getTitle()),
));
}
);
$header = id(new PHUIHeaderView())
->setHeader($header_text);
$ths = array(
phutil_tag('th', array(), pht('Days')),
@ -66,12 +51,23 @@ final class PhabricatorCountdownView extends AphrontTagView {
),
$launch_date);
$description = $countdown->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($this->getUser(), $description);
$description = phutil_tag(
'div',
array(
'class' => 'countdown-description phabricator-remarkup',
),
$description);
}
$container = celerity_generate_unique_node_id();
$content = phutil_tag(
'div',
array('class' => 'phabricator-timer', 'id' => $container),
array(
$header,
$description,
phutil_tag('table', array('class' => 'phabricator-timer-table'), array(
phutil_tag('tr', array(), $ths),
phutil_tag('tr', array(), $dashes),
@ -84,7 +80,11 @@ final class PhabricatorCountdownView extends AphrontTagView {
'container' => $container,
));
return $content;
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addClass('phabricator-timer-view')
->appendChild($content);
}
}

View file

@ -21,6 +21,7 @@ final class PhabricatorDashboardPanelCoreCustomField
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
$key = $this->getProxy()->getRawStandardFieldKey();
$this->setValueFromStorage($object->getProperty($key));
$this->didSetValueFromStorage();
}
public function applyApplicationTransactionInternalEffects(

View file

@ -92,4 +92,130 @@ abstract class DifferentialController extends PhabricatorController {
return $toc_view;
}
protected function loadDiffProperties(array $diffs) {
$diffs = mpull($diffs, null, 'getID');
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID IN (%Ld)',
array_keys($diffs));
$properties = mgroup($properties, 'getDiffID');
foreach ($diffs as $id => $diff) {
$values = idx($properties, $id, array());
$values = mpull($values, 'getData', 'getName');
$diff->attachDiffProperties($values);
}
}
protected function loadHarbormasterData(array $diffs) {
$viewer = $this->getViewer();
$diffs = mpull($diffs, null, 'getPHID');
$buildables = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withBuildablePHIDs(array_keys($diffs))
->withManualBuildables(false)
->needBuilds(true)
->needTargets(true)
->execute();
$buildables = mpull($buildables, null, 'getBuildablePHID');
foreach ($diffs as $phid => $diff) {
$diff->attachBuildable(idx($buildables, $phid));
}
$target_map = array();
foreach ($diffs as $phid => $diff) {
$target_map[$phid] = $diff->getBuildTargetPHIDs();
}
$all_target_phids = array_mergev($target_map);
if ($all_target_phids) {
$unit_messages = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$all_target_phids);
$unit_messages = mgroup($unit_messages, 'getBuildTargetPHID');
} else {
$unit_messages = array();
}
foreach ($diffs as $phid => $diff) {
$target_phids = idx($target_map, $phid, array());
$messages = array_select_keys($unit_messages, $target_phids);
$messages = array_mergev($messages);
$diff->attachUnitMessages($messages);
}
// For diffs with no messages, look for legacy unit messages stored on the
// diff itself.
foreach ($diffs as $phid => $diff) {
if ($diff->getUnitMessages()) {
continue;
}
if (!$diff->hasDiffProperty('arc:unit')) {
continue;
}
$legacy_messages = $diff->getProperty('arc:unit');
if (!$legacy_messages) {
continue;
}
// Show the top 100 legacy lint messages. Previously, we showed some
// by default and let the user toggle the rest. With modern messages,
// we can send the user to the Harbormaster detail page. Just show
// "a lot" of messages in legacy cases to try to strike a balance
// between implementation simplicitly and compatibility.
$legacy_messages = array_slice($legacy_messages, 0, 100);
$messages = array();
foreach ($legacy_messages as $message) {
$messages[] = HarbormasterBuildUnitMessage::newFromDictionary(
new HarbormasterBuildTarget(),
$this->getModernUnitMessageDictionary($message));
}
$diff->attachUnitMessages($messages);
}
}
private function getModernUnitMessageDictionary(array $map) {
// Strip out `null` values to satisfy stricter typechecks.
foreach ($map as $key => $value) {
if ($value === null) {
unset($map[$key]);
}
}
return $map;
}
protected function getDiffTabLabels(array $diffs) {
// Make sure we're only going to render unique diffs.
$diffs = mpull($diffs, null, 'getID');
$labels = array(pht('Left'), pht('Right'));
$results = array();
foreach ($diffs as $diff) {
if (count($diffs) == 2) {
$label = array_shift($labels);
$label = pht('%s (Diff %d)', $label, $diff->getID());
} else {
$label = pht('Diff %d', $diff->getID());
}
$results[] = array(
$label,
$diff,
);
}
return $results;
}
}

View file

@ -102,10 +102,8 @@ final class DifferentialRevisionViewController extends DifferentialController {
}
}
$props = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d',
$target_manual->getID());
$props = mpull($props, 'getData', 'getName');
$this->loadDiffProperties($diffs);
$props = $target_manual->getDiffProperties();
$object_phids = array_merge(
$revision->getReviewers(),
@ -256,23 +254,17 @@ final class DifferentialRevisionViewController extends DifferentialController {
array($diff_vs, $target->getID()));
$detail_diffs = mpull($detail_diffs, null, 'getPHID');
$buildables = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withBuildablePHIDs(array_keys($detail_diffs))
->withManualBuildables(false)
->needBuilds(true)
->needTargets(true)
->execute();
$buildables = mpull($buildables, null, 'getBuildablePHID');
foreach ($detail_diffs as $diff_phid => $detail_diff) {
$detail_diff->attachBuildable(idx($buildables, $diff_phid));
}
$this->loadHarbormasterData($detail_diffs);
$diff_detail_box = $this->buildDiffDetailView(
$detail_diffs,
$revision,
$field_list);
$unit_box = $this->buildUnitMessagesView(
$target,
$revision);
$comment_view = $this->buildTransactions(
$revision,
$diff_vs ? $diffs[$diff_vs] : $target,
@ -469,6 +461,7 @@ final class DifferentialRevisionViewController extends DifferentialController {
$operations_box,
$revision_detail_box,
$diff_detail_box,
$unit_box,
$page_pane,
);
@ -972,18 +965,9 @@ final class DifferentialRevisionViewController extends DifferentialController {
return null;
}
// Make sure we're only going to render unique diffs.
$diffs = mpull($diffs, null, 'getID');
$labels = array(pht('Left'), pht('Right'));
$property_lists = array();
foreach ($diffs as $diff) {
if (count($diffs) == 2) {
$label = array_shift($labels);
$label = pht('%s (Diff %d)', $label, $diff->getID());
} else {
$label = pht('Diff %d', $diff->getID());
}
foreach ($this->getDiffTabLabels($diffs) as $tab) {
list($label, $diff) = $tab;
$property_lists[] = array(
$label,
@ -1083,4 +1067,44 @@ final class DifferentialRevisionViewController extends DifferentialController {
->setOperation($operation);
}
private function buildUnitMessagesView(
$diff,
DifferentialRevision $revision) {
$viewer = $this->getViewer();
if (!$diff->getUnitMessages()) {
return null;
}
$interesting_messages = array();
foreach ($diff->getUnitMessages() as $message) {
switch ($message->getResult()) {
case ArcanistUnitTestResult::RESULT_PASS:
case ArcanistUnitTestResult::RESULT_SKIP:
break;
default:
$interesting_messages[] = $message;
break;
}
}
if (!$interesting_messages) {
return null;
}
$excuse = null;
if ($diff->hasDiffProperty('arc:unit-excuse')) {
$excuse = $diff->getProperty('arc:unit-excuse');
}
return id(new HarbormasterUnitSummaryView())
->setUser($viewer)
->setExcuse($excuse)
->setBuildable($diff->getBuildable())
->setUnitMessages($diff->getUnitMessages())
->setLimit(5)
->setShowViewAll(true);
}
}

View file

@ -1,7 +1,7 @@
<?php
final class DifferentialUnitField
extends DifferentialHarbormasterField {
extends DifferentialCustomField {
public function getFieldKey() {
return 'differential:unit';
@ -31,51 +31,6 @@ final class DifferentialUnitField
return $this->getFieldName();
}
protected function getLegacyProperty() {
return 'arc:unit';
}
protected function getDiffPropertyKeys() {
return array(
'arc:unit',
'arc:unit-excuse',
);
}
protected function loadHarbormasterTargetMessages(array $target_phids) {
return id(new HarbormasterBuildUnitMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls)',
$target_phids);
}
protected function newModernMessage(array $message) {
return HarbormasterBuildUnitMessage::newFromDictionary(
new HarbormasterBuildTarget(),
$this->getModernUnitMessageDictionary($message));
}
protected function newHarbormasterMessageView(array $messages) {
foreach ($messages as $key => $message) {
switch ($message->getResult()) {
case ArcanistUnitTestResult::RESULT_PASS:
case ArcanistUnitTestResult::RESULT_SKIP:
// Don't show "Pass" or "Skip" in the UI since they aren't very
// interesting. The user can click through to the full results if
// they want details.
unset($messages[$key]);
break;
}
}
if (!$messages) {
return null;
}
return id(new HarbormasterUnitPropertyView())
->setLimit(10)
->setUnitMessages($messages);
}
public function getWarningsForDetailView() {
$status = $this->getObject()->getActiveDiff()->getUnitStatus();
@ -94,9 +49,7 @@ final class DifferentialUnitField
return $warnings;
}
protected function renderHarbormasterStatus(
DifferentialDiff $diff,
array $messages) {
public function renderDiffPropertyViewValue(DifferentialDiff $diff) {
$colors = array(
DifferentialUnitStatus::UNIT_NONE => 'grey',
@ -110,89 +63,15 @@ final class DifferentialUnitField
$message = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage($diff);
$note = array();
$groups = mgroup($messages, 'getResult');
$groups = array_select_keys(
$groups,
array(
ArcanistUnitTestResult::RESULT_FAIL,
ArcanistUnitTestResult::RESULT_BROKEN,
ArcanistUnitTestResult::RESULT_UNSOUND,
ArcanistUnitTestResult::RESULT_SKIP,
ArcanistUnitTestResult::RESULT_PASS,
)) + $groups;
foreach ($groups as $result => $group) {
$count = phutil_count($group);
switch ($result) {
case ArcanistUnitTestResult::RESULT_PASS:
$note[] = pht('%s Passed Test(s)', $count);
break;
case ArcanistUnitTestResult::RESULT_FAIL:
$note[] = pht('%s Failed Test(s)', $count);
break;
case ArcanistUnitTestResult::RESULT_SKIP:
$note[] = pht('%s Skipped Test(s)', $count);
break;
case ArcanistUnitTestResult::RESULT_BROKEN:
$note[] = pht('%s Broken Test(s)', $count);
break;
case ArcanistUnitTestResult::RESULT_UNSOUND:
$note[] = pht('%s Unsound Test(s)', $count);
break;
default:
$note[] = pht('%s Other Test(s)', $count);
break;
}
}
$buildable = $diff->getBuildable();
if ($buildable) {
$full_results = '/harbormaster/unit/'.$buildable->getID().'/';
$note[] = phutil_tag(
'a',
array(
'href' => $full_results,
),
pht('View Full Results'));
}
$excuse = $diff->getProperty('arc:unit-excuse');
if (strlen($excuse)) {
$excuse = array(
phutil_tag('strong', array(), pht('Excuse:')),
' ',
phutil_escape_html_newlines($excuse),
);
$note[] = $excuse;
}
$note = phutil_implode_html(" \xC2\xB7 ", $note);
$status = id(new PHUIStatusListView())
->addItem(
id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_STAR, $icon_color)
->setTarget($message)
->setNote($note));
->setTarget($message));
return $status;
}
private function getModernUnitMessageDictionary(array $map) {
// Strip out `null` values to satisfy stricter typechecks.
foreach ($map as $key => $value) {
if ($value === null) {
unset($map[$key]);
}
}
// TODO: Remap more stuff here?
return $map;
}
}

View file

@ -40,6 +40,8 @@ final class DifferentialDiff
private $properties = array();
private $buildable = self::ATTACHABLE;
private $unitMessages = self::ATTACHABLE;
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
@ -334,6 +336,20 @@ final class DifferentialDiff
return $this->assertAttachedKey($this->properties, $key);
}
public function hasDiffProperty($key) {
$properties = $this->getDiffProperties();
return array_key_exists($key, $properties);
}
public function attachDiffProperties(array $properties) {
$this->properties = $properties;
return $this;
}
public function getDiffProperties() {
return $this->assertAttached($this->properties);
}
public function attachBuildable(HarbormasterBuildable $buildable = null) {
$this->buildable = $buildable;
return $this;
@ -391,6 +407,17 @@ final class DifferentialDiff
}
public function attachUnitMessages(array $unit_messages) {
$this->unitMessages = $unit_messages;
return $this;
}
public function getUnitMessages() {
return $this->assertAttached($this->unitMessages);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@ -429,6 +456,15 @@ final class DifferentialDiff
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildableDisplayPHID() {
$container_phid = $this->getHarbormasterContainerPHID();
if ($container_phid) {
return $container_phid;
}
return $this->getHarbormasterBuildablePHID();
}
public function getHarbormasterBuildablePHID() {
return $this->getPHID();
}

View file

@ -432,6 +432,10 @@ final class DifferentialRevision extends DifferentialDAO
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildableDisplayPHID() {
return $this->getHarbormasterContainerPHID();
}
public function getHarbormasterBuildablePHID() {
return $this->loadActiveDiff()->getPHID();
}

View file

@ -30,10 +30,6 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
return self::GROUP_UTILITIES;
}
public function isPrototype() {
return true;
}
public function getHelpDocumentationArticles(PhabricatorUser $viewer) {
return array(
array(
@ -60,8 +56,8 @@ final class PhabricatorDrydockApplication extends PhabricatorApplication {
'authorizations/(?:query/(?P<queryKey>[^/]+)/)?' =>
'DrydockAuthorizationListController',
),
'create/' => 'DrydockBlueprintCreateController',
'edit/(?:(?P<id>[1-9]\d*)/)?' => 'DrydockBlueprintEditController',
$this->getEditRoutePattern('edit/')
=> 'DrydockBlueprintEditController',
),
'(?P<type>resource)/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?' => 'DrydockResourceListController',

View file

@ -15,6 +15,10 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
return pht('Almanac Hosts');
}
public function getBlueprintIcon() {
return 'fa-server';
}
public function getDescription() {
return pht(
'Allows Drydock to lease existing hosts defined in an Almanac service '.

View file

@ -15,6 +15,10 @@ abstract class DrydockBlueprintImplementation extends Phobject {
abstract public function getBlueprintName();
abstract public function getDescription();
public function getBlueprintIcon() {
return 'fa-map-o';
}
public function getFieldSpecifications() {
$fields = array();

View file

@ -13,6 +13,10 @@ final class DrydockWorkingCopyBlueprintImplementation
return pht('Working Copy');
}
public function getBlueprintIcon() {
return 'fa-folder-open';
}
public function getDescription() {
return pht('Allows Drydock to check out working copies of repositories.');
}

View file

@ -3,24 +3,18 @@
abstract class DrydockBlueprintController
extends DrydockController {
public function buildSideNavView() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new DrydockBlueprintSearchEngine())
->setViewer($this->getRequest()->getUser())
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
public function buildApplicationMenu() {
return $this->newApplicationMenu()
->setSearchEngine(new DrydockBlueprintSearchEngine());
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Blueprints'),
$this->getApplicationURI('blueprint/'));
return $crumbs;
}

View file

@ -1,79 +0,0 @@
<?php
final class DrydockBlueprintCreateController
extends DrydockBlueprintController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$this->requireApplicationCapability(
DrydockCreateBlueprintsCapability::CAPABILITY);
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
$errors = array();
$e_blueprint = null;
if ($request->isFormPost()) {
$class = $request->getStr('blueprint-type');
if (!isset($implementations[$class])) {
$e_blueprint = pht('Required');
$errors[] = pht('You must choose a blueprint type.');
}
if (!$errors) {
$edit_uri = $this->getApplicationURI('blueprint/edit/?class='.$class);
return id(new AphrontRedirectResponse())->setURI($edit_uri);
}
}
$control = id(new AphrontFormRadioButtonControl())
->setName('blueprint-type')
->setLabel(pht('Blueprint Type'))
->setError($e_blueprint);
foreach ($implementations as $implementation_name => $implementation) {
$disabled = !$implementation->isEnabled();
$control->addButton(
$implementation_name,
$implementation->getBlueprintName(),
array(
pht('Provides: %s', $implementation->getType()),
phutil_tag('br'),
phutil_tag('br'),
$implementation->getDescription(),
),
$disabled ? 'disabled' : null,
$disabled);
}
$title = pht('Create New Blueprint');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('New Blueprint'));
$form = id(new AphrontFormView())
->setUser($viewer)
->appendChild($control)
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($this->getApplicationURI('blueprint/'))
->setValue(pht('Continue')));
$box = id(new PHUIObjectBoxView())
->setFormErrors($errors)
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
}
}

View file

@ -3,168 +3,95 @@
final class DrydockBlueprintEditController extends DrydockBlueprintController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$engine = id(new DrydockBlueprintEditEngine())
->setController($this);
$id = $request->getURIData('id');
if ($id) {
$blueprint = id(new DrydockBlueprintQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$blueprint) {
return new Aphront404Response();
}
$impl = $blueprint->getImplementation();
$cancel_uri = $this->getApplicationURI('blueprint/'.$id.'/');
} else {
if (!$id) {
$this->requireApplicationCapability(
DrydockCreateBlueprintsCapability::CAPABILITY);
$class = $request->getStr('class');
$type = $request->getStr('blueprintType');
$impl = DrydockBlueprintImplementation::getNamedImplementation($class);
$impl = DrydockBlueprintImplementation::getNamedImplementation($type);
if (!$impl || !$impl->isEnabled()) {
return new Aphront400Response();
return $this->buildTypeSelectionResponse();
}
$blueprint = DrydockBlueprint::initializeNewBlueprint($viewer)
->setClassName($class)
->attachImplementation($impl);
$cancel_uri = $this->getApplicationURI('blueprint/');
$engine
->addContextParameter('blueprintType', $type)
->setBlueprintImplementation($impl);
}
$field_list = PhabricatorCustomField::getObjectFields(
$blueprint,
PhabricatorCustomField::ROLE_EDIT);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($blueprint);
return $engine->buildResponse();
}
private function buildTypeSelectionResponse() {
$request = $this->getRequest();
$viewer = $this->getViewer();
$implementations =
DrydockBlueprintImplementation::getAllBlueprintImplementations();
$v_name = $blueprint->getBlueprintName();
$e_name = true;
$errors = array();
$validation_exception = null;
$e_blueprint = null;
if ($request->isFormPost()) {
$v_view_policy = $request->getStr('viewPolicy');
$v_edit_policy = $request->getStr('editPolicy');
$v_name = $request->getStr('name');
if (!strlen($v_name)) {
$e_name = pht('Required');
$errors[] = pht('You must name this blueprint.');
}
if (!$errors) {
$xactions = array();
$xactions = $field_list->buildFieldTransactionsFromRequest(
new DrydockBlueprintTransaction(),
$request);
$xactions[] = id(new DrydockBlueprintTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
->setNewValue($v_view_policy);
$xactions[] = id(new DrydockBlueprintTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
->setNewValue($v_edit_policy);
$xactions[] = id(new DrydockBlueprintTransaction())
->setTransactionType(DrydockBlueprintTransaction::TYPE_NAME)
->setNewValue($v_name);
$editor = id(new DrydockBlueprintEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true);
try {
$editor->applyTransactions($blueprint, $xactions);
$id = $blueprint->getID();
$save_uri = $this->getApplicationURI("blueprint/{$id}/");
return id(new AphrontRedirectResponse())->setURI($save_uri);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$class = $request->getStr('blueprintType');
if (!isset($implementations[$class])) {
$e_blueprint = pht('Required');
$errors[] = pht('You must choose a blueprint type.');
}
}
$control = id(new AphrontFormRadioButtonControl())
->setName('blueprintType')
->setLabel(pht('Blueprint Type'))
->setError($e_blueprint);
foreach ($implementations as $implementation_name => $implementation) {
$disabled = !$implementation->isEnabled();
$impl_icon = $implementation->getBlueprintIcon();
$impl_name = $implementation->getBlueprintName();
$impl_icon = id(new PHUIIconView())
->setIcon($impl_icon, 'lightgreytext');
$control->addButton(
$implementation_name,
array($impl_icon, ' ', $impl_name),
array(
pht('Provides: %s', $implementation->getType()),
phutil_tag('br'),
phutil_tag('br'),
$implementation->getDescription(),
),
$disabled ? 'disabled' : null,
$disabled);
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($blueprint)
->execute();
$title = pht('Create New Blueprint');
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('New Blueprint'));
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('class', $request->getStr('class'))
->appendChild($control)
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Name'))
->setName('name')
->setValue($v_name)
->setError($e_name))
->appendChild(
id(new AphrontFormStaticControl())
->setLabel(pht('Blueprint Type'))
->setValue($impl->getBlueprintName()))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('viewPolicy')
->setPolicyObject($blueprint)
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicies($policies))
->appendChild(
id(new AphrontFormPolicyControl())
->setName('editPolicy')
->setPolicyObject($blueprint)
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicies($policies));
$field_list->appendFieldsToForm($form);
$crumbs = $this->buildApplicationCrumbs();
if ($blueprint->getID()) {
$title = pht('Edit Blueprint');
$header = pht('Edit Blueprint %d', $blueprint->getID());
$crumbs->addTextCrumb(pht('Blueprint %d', $blueprint->getID()));
$crumbs->addTextCrumb(pht('Edit'));
$submit = pht('Save Blueprint');
} else {
$title = pht('New Blueprint');
$header = pht('New Blueprint');
$crumbs->addTextCrumb(pht('New Blueprint'));
$submit = pht('Create Blueprint');
}
$form->appendChild(
id(new AphrontFormSubmitControl())
->setValue($submit)
->addCancelButton($cancel_uri));
->addCancelButton($this->getApplicationURI('blueprint/'))
->setValue(pht('Continue')));
$box = id(new PHUIObjectBoxView())
->setHeaderText($header)
->setValidationException($validation_exception)
->setFormErrors($errors)
->setHeaderText($title)
->setForm($form);
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($box);
}
}

View file

@ -7,29 +7,18 @@ final class DrydockBlueprintListController extends DrydockBlueprintController {
}
public function handleRequest(AphrontRequest $request) {
$querykey = $request->getURIData('queryKey');
$request = $this->getRequest();
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($querykey)
->setSearchEngine(new DrydockBlueprintSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
return id(new DrydockBlueprintSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$can_create = $this->hasApplicationCapability(
DrydockCreateBlueprintsCapability::CAPABILITY);
$crumbs = parent::buildApplicationCrumbs();
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('New Blueprint'))
->setHref($this->getApplicationURI('/blueprint/create/'))
->setDisabled(!$can_create)
->setWorkflow(!$can_create)
->setIcon('fa-plus-square'));
id(new DrydockBlueprintEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}

View file

@ -127,8 +127,12 @@ final class DrydockBlueprintViewController extends DrydockBlueprintController {
private function buildPropertyListView(
DrydockBlueprint $blueprint,
PhabricatorActionListView $actions) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($blueprint);
$view = new PHUIPropertyListView();
$view->setActionList($actions);
$view->addProperty(

View file

@ -0,0 +1,11 @@
<?php
abstract class DrydockRepositoryOperationController
extends DrydockController {
public function buildApplicationMenu() {
return $this->newApplicationMenu()
->setSearchEngine(new DrydockRepositoryOperationSearchEngine());
}
}

View file

@ -1,7 +1,7 @@
<?php
final class DrydockRepositoryOperationDismissController
extends DrydockController {
extends DrydockRepositoryOperationController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();

View file

@ -1,37 +1,16 @@
<?php
final class DrydockRepositoryOperationListController
extends DrydockController {
extends DrydockRepositoryOperationController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$query_key = $request->getURIData('queryKey');
$engine = new DrydockRepositoryOperationSearchEngine();
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($query_key)
->setSearchEngine($engine)
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function buildSideNavView() {
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
$engine = id(new DrydockRepositoryOperationSearchEngine())
->setViewer($this->getViewer());
$engine->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
return id(new DrydockRepositoryOperationSearchEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -1,7 +1,7 @@
<?php
final class DrydockRepositoryOperationStatusController
extends DrydockController {
extends DrydockRepositoryOperationController {
public function shouldAllowPublic() {
return true;

View file

@ -1,7 +1,7 @@
<?php
final class DrydockRepositoryOperationViewController
extends DrydockController {
extends DrydockRepositoryOperationController {
public function shouldAllowPublic() {
return true;
@ -50,16 +50,14 @@ final class DrydockRepositoryOperationViewController
->setUser($viewer)
->setOperation($operation);
return $this->buildApplicationPage(
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$crumbs,
$object_box,
$status_view,
),
array(
'title' => $title,
));
}
private function buildActionListView(DrydockRepositoryOperation $operation) {

View file

@ -28,6 +28,7 @@ final class DrydockBlueprintCoreCustomField
public function readValueFromObject(PhabricatorCustomFieldInterface $object) {
$key = $this->getProxy()->getRawStandardFieldKey();
$this->setValueFromStorage($object->getDetail($key));
$this->didSetValueFromStorage();
}
public function applyApplicationTransactionInternalEffects(

View file

@ -0,0 +1,115 @@
<?php
final class DrydockBlueprintEditEngine
extends PhabricatorEditEngine {
private $blueprintImplementation;
const ENGINECONST = 'drydock.blueprint';
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Drydock Blueprints');
}
public function getSummaryHeader() {
return pht('Edit Drydock Blueprint Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Drydock blueprints.');
}
public function getEngineApplicationClass() {
return 'PhabricatorDrydockApplication';
}
public function setBlueprintImplementation(
DrydockBlueprintImplementation $impl) {
$this->blueprintImplementation = $impl;
return $this;
}
public function getBlueprintImplementation() {
return $this->blueprintImplementation;
}
protected function newEditableObject() {
$viewer = $this->getViewer();
$blueprint = DrydockBlueprint::initializeNewBlueprint($viewer);
$impl = $this->getBlueprintImplementation();
if ($impl) {
$blueprint
->setClassName(get_class($impl))
->attachImplementation(clone $impl);
}
return $blueprint;
}
protected function newObjectQuery() {
return new DrydockBlueprintQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Blueprint');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Blueprint');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Blueprint: %s', $object->getBlueprintName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Blueprint');
}
protected function getObjectCreateShortText() {
return pht('Create Blueprint');
}
protected function getEditorURI() {
return '/drydock/blueprint/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/drydock/blueprint/';
}
protected function getObjectViewURI($object) {
$id = $object->getID();
return "/drydock/blueprint/{$id}/";
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
DrydockCreateBlueprintsCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
$impl = $object->getImplementation();
return array(
id(new PhabricatorStaticEditField())
->setKey('type')
->setLabel(pht('Blueprint Type'))
->setDescription(pht('Type of blueprint.'))
->setValue($impl->getBlueprintName()),
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setDescription(pht('Name of the blueprint.'))
->setTransactionType(DrydockBlueprintTransaction::TYPE_NAME)
->setIsRequired(true)
->setValue($object->getBlueprintName()),
);
}
}

View file

@ -11,6 +11,10 @@ final class DrydockBlueprintEditor
return pht('Drydock Blueprints');
}
protected function supportsSearch() {
return true;
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
@ -80,4 +84,36 @@ final class DrydockBlueprintEditor
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case DrydockBlueprintTransaction::TYPE_NAME:
$missing = $this->validateIsEmptyTextField(
$object->getBlueprintName(),
$xactions);
if ($missing) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('You must choose a name for this blueprint.'),
nonempty(last($xactions), null));
$error->setIsMissingFieldError(true);
$errors[] = $error;
continue;
}
break;
}
return $errors;
}
}

View file

@ -39,6 +39,12 @@ final class DrydockBlueprintQuery extends DrydockQuery {
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new DrydockBlueprintNameNgrams(),
$ngrams);
}
public function newResultObject() {
return new DrydockBlueprint();
}

View file

@ -18,6 +18,10 @@ final class DrydockBlueprintSearchEngine
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['match'] !== null) {
$query->withNameNgrams($map['match']);
}
if ($map['isDisabled'] !== null) {
$query->withDisabled($map['isDisabled']);
}
@ -27,6 +31,10 @@ final class DrydockBlueprintSearchEngine
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains'))
->setKey('match')
->setDescription(pht('Search for blueprints by name substring.')),
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Disabled'))
->setKey('isDisabled')
@ -69,15 +77,29 @@ final class DrydockBlueprintSearchEngine
assert_instances_of($blueprints, 'DrydockBlueprint');
$viewer = $this->requireViewer();
if ($blueprints) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($blueprints, 'getPHID'))
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
}
$view = new PHUIObjectItemListView();
foreach ($blueprints as $blueprint) {
$impl = $blueprint->getImplementation();
$item = id(new PHUIObjectItemView())
->setHeader($blueprint->getBlueprintName())
->setHref($this->getApplicationURI('/blueprint/'.$blueprint->getID()))
->setHref($blueprint->getURI())
->setObjectName(pht('Blueprint %d', $blueprint->getID()));
if (!$blueprint->getImplementation()->isEnabled()) {
if (!$impl->isEnabled()) {
$item->setDisabled(true);
$item->addIcon('fa-chain-broken grey', pht('Implementation'));
}
@ -87,7 +109,24 @@ final class DrydockBlueprintSearchEngine
$item->addIcon('fa-ban grey', pht('Disabled'));
}
$item->addAttribute($blueprint->getImplementation()->getBlueprintName());
$impl_icon = $impl->getBlueprintIcon();
$impl_name = $impl->getBlueprintName();
$impl_icon = id(new PHUIIconView())
->setIcon($impl_icon, 'lightgreytext');
$item->addAttribute(array($impl_icon, ' ', $impl_name));
$phid = $blueprint->getPHID();
$project_phids = $edge_query->getDestinationPHIDs(array($phid));
if ($project_phids) {
$project_handles = $viewer->loadHandles($project_phids);
$item->addAttribute(
id(new PHUIHandleTagListView())
->setLimit(4)
->setSlim(true)
->setHandles($project_handles));
}
$view->addItem($item);
}

View file

@ -70,7 +70,8 @@ final class DrydockRepositoryOperationSearchEngine
$icon = DrydockRepositoryOperation::getOperationStateIcon($state);
$name = DrydockRepositoryOperation::getOperationStateName($state);
$item->addIcon($icon, $name);
$item->setStatusIcon($icon, $name);
$item->addByline(
array(
pht('Via:'),
@ -78,13 +79,14 @@ final class DrydockRepositoryOperationSearchEngine
$viewer->renderHandle($operation->getAuthorPHID()),
));
$item->addAttribute(
$viewer->renderHandle(
$operation->getObjectPHID()));
$object_phid = $operation->getObjectPHID();
$repository_phid = $operation->getRepositoryPHID();
$item->addAttribute(
$viewer->renderHandle(
$operation->getRepositoryPHID()));
$item->addAttribute($viewer->renderHandle($object_phid));
if ($repository_phid !== $object_phid) {
$item->addAttribute($viewer->renderHandle($repository_phid));
}
$view->addItem($item);
}

View file

@ -8,7 +8,9 @@ final class DrydockBlueprint extends DrydockDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorCustomFieldInterface {
PhabricatorCustomFieldInterface,
PhabricatorNgramsInterface,
PhabricatorProjectInterface {
protected $className;
protected $blueprintName;
@ -118,6 +120,11 @@ final class DrydockBlueprint extends DrydockDAO
return $log->save();
}
public function getURI() {
$id = $this->getID();
return "/drydock/blueprint/{$id}/";
}
/* -( Allocating Resources )----------------------------------------------- */
@ -343,4 +350,14 @@ final class DrydockBlueprint extends DrydockDAO
}
/* -( PhabricatorNgramInterface )------------------------------------------ */
public function newNgrams() {
return array(
id(new DrydockBlueprintNameNgrams())
->setValue($this->getBlueprintName()),
);
}
}

View file

@ -0,0 +1,18 @@
<?php
final class DrydockBlueprintNameNgrams
extends PhabricatorSearchNgrams {
public function getNgramKey() {
return 'blueprintname';
}
public function getColumnName() {
return 'blueprintName';
}
public function getApplicationName() {
return 'drydock';
}
}

View file

@ -0,0 +1,9 @@
<?php
final class DrydockSchemaSpec extends PhabricatorConfigSchemaSpec {
public function buildSchemata() {
$this->buildEdgeSchemata(new DrydockBlueprint());
}
}

View file

@ -26,11 +26,20 @@ final class DrydockBlueprintDatasource
->execute();
$results = array();
foreach ($handles as $handle) {
$results[] = id(new PhabricatorTypeaheadResult())
->setName($handle->getName())
foreach ($blueprints as $blueprint) {
$handle = $handles[$blueprint->getPHID()];
$result = id(new PhabricatorTypeaheadResult())
->setName($handle->getFullName())
->setPHID($handle->getPHID());
if ($blueprint->getIsDisabled()) {
$result->setClosed(pht('Disabled'));
}
$results[] = $result;
}
return $results;
}
}

View file

@ -7,7 +7,6 @@ final class FundInitiativeViewController
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
@ -22,6 +21,7 @@ final class FundInitiativeViewController
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($initiative->getMonogram());
$crumbs->setBorder(true);
$title = pht(
'%s %s',
@ -43,32 +43,32 @@ final class FundInitiativeViewController
->setHeader($initiative->getName())
->setUser($viewer)
->setPolicyObject($initiative)
->setStatus($status_icon, $status_color, $status_name);
->setStatus($status_icon, $status_color, $status_name)
->setHeaderIcon('fa-heart');
$properties = $this->buildPropertyListView($initiative);
$actions = $this->buildActionListView($initiative);
$properties->setActionList($actions);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$details = $this->buildPropertySectionView($initiative);
$timeline = $this->buildTransactionTimeline(
$initiative,
new FundInitiativeTransactionQuery());
$timeline
->setShouldTerminate(true);
$timeline->setShouldTerminate(true);
return $this->buildApplicationPage(
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setMainColumn($timeline)
->setPropertyList($properties)
->addPropertySection(pht('DETAILS'), $details)
->setActionList($actions);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($initiative->getPHID()))
->appendChild(
array(
$crumbs,
$box,
$timeline,
),
array(
'title' => $title,
'pageObjects' => array($initiative->getPHID()),
$view,
));
}
@ -79,6 +79,17 @@ final class FundInitiativeViewController
->setUser($viewer)
->setObject($initiative);
$view->invokeWillRenderEvent();
return $view;
}
private function buildPropertySectionView(FundInitiative $initiative) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$owner_phid = $initiative->getOwnerPHID();
$merchant_phid = $initiative->getMerchantPHID();
@ -94,8 +105,6 @@ final class FundInitiativeViewController
pht('Total Funding'),
$initiative->getTotalAsCurrency()->formatForDisplay());
$view->invokeWillRenderEvent();
$description = $initiative->getDescription();
if (strlen($description)) {
$description = new PHUIRemarkupView($viewer, $description);

View file

@ -76,16 +76,17 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication {
=> 'HarbormasterBuildActionController',
),
'plan/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'HarbormasterPlanListController',
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
$this->getQueryRoutePattern() => 'HarbormasterPlanListController',
$this->getEditRoutePattern('edit/')
=> 'HarbormasterPlanEditController',
'order/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanOrderController',
'disable/(?P<id>\d+)/' => 'HarbormasterPlanDisableController',
'run/(?P<id>\d+)/' => 'HarbormasterPlanRunController',
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
),
'unit/' => array(
'(?P<id>\d+)/' => 'HarbormasterUnitMessagesController',
'(?P<id>\d+)/' => 'HarbormasterUnitMessageListController',
'view/(?P<id>\d+)/' => 'HarbormasterUnitMessageViewController',
),
'lint/' => array(
'(?P<id>\d+)/' => 'HarbormasterLintMessagesController',

View file

@ -0,0 +1,90 @@
<?php
final class HarbormasterUnitStatus
extends Phobject {
public static function getUnitStatusIcon($status) {
$map = self::getUnitStatusDictionary($status);
$default = 'fa-question-circle';
return idx($map, 'icon', $default);
}
public static function getUnitStatusColor($status) {
$map = self::getUnitStatusDictionary($status);
$default = 'violet';
return idx($map, 'color', $default);
}
public static function getUnitStatusLabel($status) {
$map = self::getUnitStatusDictionary($status);
$default = pht('Unknown Status ("%s")', $status);
return idx($map, 'label', $default);
}
public static function getUnitStatusSort($status) {
$map = self::getUnitStatusDictionary($status);
$default = 'N';
return idx($map, 'sort', $default);
}
private static function getUnitStatusDictionary($status) {
$map = self::getUnitStatusMap();
$default = array();
return idx($map, $status, $default);
}
public static function getUnitStatusCountLabel($status, $count) {
$count = new PhutilNumber($count);
switch ($status) {
case ArcanistUnitTestResult::RESULT_FAIL:
return pht('%s Failed Test(s)', $count);
case ArcanistUnitTestResult::RESULT_BROKEN:
return pht('%s Broken Test(s)', $count);
case ArcanistUnitTestResult::RESULT_UNSOUND:
return pht('%s Unsound Test(s)', $count);
case ArcanistUnitTestResult::RESULT_PASS:
return pht('%s Passed Test(s)', $count);
case ArcanistUnitTestResult::RESULT_SKIP:
return pht('%s Skipped Test(s)', $count);
}
return pht('%s Other Test(s)', $count);
}
private static function getUnitStatusMap() {
return array(
ArcanistUnitTestResult::RESULT_FAIL => array(
'label' => pht('Failed'),
'icon' => 'fa-times',
'color' => 'red',
'sort' => 'A',
),
ArcanistUnitTestResult::RESULT_BROKEN => array(
'label' => pht('Broken'),
'icon' => 'fa-bomb',
'color' => 'indigo',
'sort' => 'B',
),
ArcanistUnitTestResult::RESULT_UNSOUND => array(
'label' => pht('Unsound'),
'icon' => 'fa-exclamation-triangle',
'color' => 'yellow',
'sort' => 'C',
),
ArcanistUnitTestResult::RESULT_PASS => array(
'label' => pht('Passed'),
'icon' => 'fa-check',
'color' => 'green',
'sort' => 'D',
),
ArcanistUnitTestResult::RESULT_SKIP => array(
'label' => pht('Skipped'),
'icon' => 'fa-fast-forward',
'color' => 'blue',
'sort' => 'E',
),
);
}
}

View file

@ -7,34 +7,21 @@ final class HarbormasterBuildableListController extends HarbormasterController {
}
public function handleRequest(AphrontRequest $request) {
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine(new HarbormasterBuildableSearchEngine())
->setNavigation($this->buildSideNavView());
$items = array();
return $this->delegateToController($controller);
}
$items[] = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_LABEL)
->setName(pht('Build Plans'));
public function buildSideNavView($for_app = false) {
$user = $this->getRequest()->getUser();
$items[] = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_LINK)
->setName(pht('Manage Build Plans'))
->setHref($this->getApplicationURI('plan/'));
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
id(new HarbormasterBuildableSearchEngine())
->setViewer($user)
->addNavigationItems($nav->getMenu());
$nav->addLabel(pht('Build Plans'));
$nav->addFilter('plan/', pht('Manage Build Plans'));
$nav->selectFilter(null);
return $nav;
}
public function buildApplicationMenu() {
return $this->buildSideNavView(true)->getMenu();
return id(new HarbormasterBuildableSearchEngine())
->setController($this)
->setNavigationItems($items)
->buildResponse();
}
}

View file

@ -9,8 +9,6 @@ final class HarbormasterBuildableViewController
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withIDs(array($request->getURIData('id')))
->needBuildableHandles(true)
->needContainerHandles(true)
->executeOne();
if (!$buildable) {
return new Aphront404Response();
@ -167,15 +165,18 @@ final class HarbormasterBuildableViewController
->setActionList($actions);
$box->addPropertyList($properties);
if ($buildable->getContainerHandle() !== null) {
$container_phid = $buildable->getContainerPHID();
$buildable_phid = $buildable->getBuildablePHID();
if ($container_phid) {
$properties->addProperty(
pht('Container'),
$buildable->getContainerHandle()->renderLink());
$viewer->renderHandle($container_phid));
}
$properties->addProperty(
pht('Buildable'),
$buildable->getBuildableHandle()->renderLink());
$viewer->renderHandle($buildable_phid));
$properties->addProperty(
pht('Origin'),
@ -335,25 +336,11 @@ final class HarbormasterBuildableViewController
}
if ($unit_data) {
$unit_table = id(new HarbormasterUnitPropertyView())
->setUser($viewer)
->setLimit(25)
->setUnitMessages($unit_data);
$unit_href = $this->getApplicationURI('unit/'.$buildable->getID().'/');
$unit_header = id(new PHUIHeaderView())
->setHeader(pht('Unit Tests'))
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setHref($unit_href)
->setIcon('fa-list-ul')
->setText('View All'));
$unit = id(new PHUIObjectBoxView())
->setHeader($unit_header)
->setTable($unit_table);
$unit = id(new HarbormasterUnitSummaryView())
->setBuildable($buildable)
->setUnitMessages($unit_data)
->setShowViewAll(true)
->setLimit(5);
} else {
$unit = null;
}

View file

@ -2,6 +2,11 @@
abstract class HarbormasterController extends PhabricatorController {
public function buildApplicationMenu() {
return $this->newApplicationMenu()
->setSearchEngine(new HarbormasterBuildableSearchEngine());
}
protected function addBuildableCrumb(
PHUICrumbsView $crumbs,
HarbormasterBuildable $buildable) {

View file

@ -2,6 +2,11 @@
abstract class HarbormasterPlanController extends HarbormasterController {
public function buildApplicationMenu() {
return $this->newApplicationMenu()
->setSearchEngine(new HarbormasterBuildPlanSearchEngine());
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();

View file

@ -3,146 +3,9 @@
final class HarbormasterPlanEditController extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
if ($id) {
$plan = id(new HarbormasterBuildPlanQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$plan) {
return new Aphront404Response();
}
} else {
$this->requireApplicationCapability(
HarbormasterCreatePlansCapability::CAPABILITY);
$plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
}
$e_name = true;
$v_name = $plan->getName();
$v_view = $plan->getViewPolicy();
$v_edit = $plan->getEditPolicy();
$validation_exception = null;
if ($request->isFormPost()) {
$xactions = array();
$v_name = $request->getStr('name');
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
$e_name = null;
$type_name = HarbormasterBuildPlanTransaction::TYPE_NAME;
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
$xactions[] = id(new HarbormasterBuildPlanTransaction())
->setTransactionType($type_name)
->setNewValue($v_name);
$xactions[] = id(new HarbormasterBuildPlanTransaction())
->setTransactionType($type_view)
->setNewValue($v_view);
$xactions[] = id(new HarbormasterBuildPlanTransaction())
->setTransactionType($type_edit)
->setNewValue($v_edit);
$editor = id(new HarbormasterBuildPlanEditor())
->setActor($viewer)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request);
try {
$editor->applyTransactions($plan, $xactions);
return id(new AphrontRedirectResponse())
->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
} catch (PhabricatorApplicationTransactionValidationException $ex) {
$validation_exception = $ex;
$e_name = $validation_exception->getShortMessage(
HarbormasterBuildPlanTransaction::TYPE_NAME);
}
}
$is_new = (!$plan->getID());
if ($is_new) {
$title = pht('New Build Plan');
$cancel_uri = $this->getApplicationURI('plan/');
$save_button = pht('Create Build Plan');
} else {
$id = $plan->getID();
$title = pht('Edit Build Plan');
$cancel_uri = $this->getApplicationURI('plan/'.$plan->getID().'/');
$save_button = pht('Save Build Plan');
}
$policies = id(new PhabricatorPolicyQuery())
->setViewer($viewer)
->setObject($plan)
->execute();
$form = id(new AphrontFormView())
->setUser($viewer)
->appendControl(
id(new AphrontFormTextControl())
->setLabel(pht('Plan Name'))
->setName('name')
->setError($e_name)
->setValue($v_name))
->appendControl(
id(new AphrontFormPolicyControl())
->setCapability(PhabricatorPolicyCapability::CAN_VIEW)
->setPolicyObject($plan)
->setPolicies($policies)
->setValue($v_view)
->setName('viewPolicy'))
->appendControl(
id(new AphrontFormPolicyControl())
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($plan)
->setPolicies($policies)
->setValue($v_edit)
->setName('editPolicy'))
->appendControl(
id(new AphrontFormSubmitControl())
->setValue($save_button)
->addCancelButton($cancel_uri));
$box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setValidationException($validation_exception)
->setForm($form);
$crumbs = $this->buildApplicationCrumbs();
if ($is_new) {
$crumbs->addTextCrumb(pht('New Build Plan'));
} else {
$id = $plan->getID();
$crumbs->addTextCrumb(
pht('Plan %d', $id),
$this->getApplicationURI("plan/{$id}/"));
$crumbs->addTextCrumb(pht('Edit'));
}
return $this->buildApplicationPage(
array(
$crumbs,
$box,
),
array(
'title' => $title,
));
return id(new HarbormasterBuildPlanEditEngine())
->setController($this)
->buildResponse();
}
}

View file

@ -7,53 +7,19 @@ final class HarbormasterPlanListController extends HarbormasterPlanController {
}
public function handleRequest(AphrontRequest $request) {
$controller = id(new PhabricatorApplicationSearchController())
->setQueryKey($request->getURIData('queryKey'))
->setSearchEngine(new HarbormasterBuildPlanSearchEngine())
->setNavigation($this->buildSideNavView());
return $this->delegateToController($controller);
}
public function buildSideNavView($for_app = false) {
$user = $this->getRequest()->getUser();
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
if ($for_app) {
$nav->addFilter('new/', pht('New Build Plan'));
}
id(new HarbormasterBuildPlanSearchEngine())
->setViewer($user)
->addNavigationItems($nav->getMenu());
$nav->selectFilter(null);
return $nav;
}
public function buildApplicationMenu() {
return $this->buildSideNavView(true)->getMenu();
return id(new HarbormasterBuildPlanSearchEngine())
->setController($this)
->buildResponse();
}
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$can_create = $this->hasApplicationCapability(
HarbormasterCreatePlansCapability::CAPABILITY);
$crumbs->addAction(
id(new PHUIListItemView())
->setName(pht('New Build Plan'))
->setHref($this->getApplicationURI('plan/edit/'))
->setDisabled(!$can_create)
->setWorkflow(!$can_create)
->setIcon('fa-plus-square'));
id(new HarbormasterBuildPlanEditEngine())
->setViewer($this->getViewer())
->addActionToCrumbs($crumbs);
return $crumbs;
}
}

View file

@ -1,6 +1,6 @@
<?php
final class HarbormasterPlanRunController extends HarbormasterController {
final class HarbormasterPlanRunController extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();

View file

@ -187,8 +187,6 @@ final class HarbormasterPlanViewController extends HarbormasterPlanController {
if ($is_deadlocking) {
$item->setStatusIcon('fa-warning red');
}
$step_list->addItem($item);
}
$step_list->setFlush(true);

View file

@ -1,6 +1,7 @@
<?php
final class HarbormasterStepAddController extends HarbormasterController {
final class HarbormasterStepAddController
extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();

View file

@ -1,6 +1,7 @@
<?php
final class HarbormasterStepDeleteController extends HarbormasterController {
final class HarbormasterStepDeleteController
extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();

View file

@ -1,6 +1,7 @@
<?php
final class HarbormasterStepEditController extends HarbormasterController {
final class HarbormasterStepEditController
extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();

View file

@ -1,6 +1,7 @@
<?php
final class HarbormasterStepViewController extends HarbormasterController {
final class HarbormasterStepViewController
extends HarbormasterPlanController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();

View file

@ -1,6 +1,6 @@
<?php
final class HarbormasterUnitMessagesController
final class HarbormasterUnitMessageListController
extends HarbormasterController {
public function handleRequest(AphrontRequest $request) {
@ -34,14 +34,10 @@ final class HarbormasterUnitMessagesController
$unit_data = array();
}
$unit_table = id(new HarbormasterUnitPropertyView())
->setUser($viewer)
$unit = id(new HarbormasterUnitSummaryView())
->setBuildable($buildable)
->setUnitMessages($unit_data);
$unit = id(new PHUIObjectBoxView())
->setHeaderText(pht('Unit Tests'))
->setTable($unit_table);
$crumbs = $this->buildApplicationCrumbs();
$this->addBuildableCrumb($crumbs, $buildable);
$crumbs->addTextCrumb(pht('Unit Tests'));

View file

@ -0,0 +1,122 @@
<?php
final class HarbormasterUnitMessageViewController
extends HarbormasterController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$message_id = $request->getURIData('id');
$message = id(new HarbormasterBuildUnitMessage())->load($message_id);
if (!$message) {
return new Aphront404Response();
}
$build_target = id(new HarbormasterBuildTargetQuery())
->setViewer($viewer)
->withPHIDs(array($message->getBuildTargetPHID()))
->executeOne();
if (!$build_target) {
return new Aphront404Response();
}
$build = $build_target->getBuild();
$buildable = $build->getBuildable();
$buildable_id = $buildable->getID();
$id = $message->getID();
$display_name = $message->getUnitMessageDisplayName();
$status = $message->getResult();
$status_icon = HarbormasterUnitStatus::getUnitStatusIcon($status);
$status_color = HarbormasterUnitStatus::getUnitStatusColor($status);
$status_label = HarbormasterUnitStatus::getUnitStatusLabel($status);
$header = id(new PHUIHeaderView())
->setHeader($display_name)
->setStatus($status_icon, $status_color, $status_label);
$properties = $this->buildPropertyListView($message);
$actions = $this->buildActionView($message, $build);
$properties->setActionList($actions);
$unit = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
$crumbs = $this->buildApplicationCrumbs();
$this->addBuildableCrumb($crumbs, $buildable);
$crumbs->addTextCrumb(
pht('Unit Tests'),
"/harbormaster/unit/{$buildable_id}/");
$crumbs->addTextCrumb(pht('Unit %d', $id));
$title = array(
$display_name,
$buildable->getMonogram(),
);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild($unit);
}
private function buildPropertyListView(
HarbormasterBuildUnitMessage $message) {
$request = $this->getRequest();
$viewer = $request->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$view->addProperty(
pht('Run At'),
phabricator_datetime($message->getDateCreated(), $viewer));
$details = $message->getUnitMessageDetails();
if (strlen($details)) {
// TODO: Use the log view here, once it gets cleaned up.
$details = phutil_tag(
'div',
array(
'class' => 'PhabricatorMonospaced',
'style' =>
'white-space: pre-wrap; '.
'color: #666666; '.
'overflow-x: auto;',
),
$details);
} else {
$details = phutil_tag('em', array(), pht('No details provided.'));
}
$view->addSectionHeader(
pht('Details'),
PHUIPropertyListView::ICON_TESTPLAN);
$view->addTextContent($details);
return $view;
}
private function buildActionView(
HarbormasterBuildUnitMessage $message,
HarbormasterBuild $build) {
$viewer = $this->getViewer();
$view = id(new PhabricatorActionListView())
->setUser($viewer);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View Build'))
->setHref($build->getURI())
->setIcon('fa-wrench'));
return $view;
}
}

View file

@ -0,0 +1,89 @@
<?php
final class HarbormasterBuildPlanEditEngine
extends PhabricatorEditEngine {
const ENGINECONST = 'harbormaster.buildplan';
public function isEngineConfigurable() {
return false;
}
public function getEngineName() {
return pht('Harbormaster Build Plans');
}
public function getSummaryHeader() {
return pht('Edit Harbormaster Build Plan Configurations');
}
public function getSummaryText() {
return pht('This engine is used to edit Harbormaster build plans.');
}
public function getEngineApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}
protected function newEditableObject() {
$viewer = $this->getViewer();
return HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
}
protected function newObjectQuery() {
return new HarbormasterBuildPlanQuery();
}
protected function getObjectCreateTitleText($object) {
return pht('Create Build Plan');
}
protected function getObjectCreateButtonText($object) {
return pht('Create Build Plan');
}
protected function getObjectEditTitleText($object) {
return pht('Edit Build Plan: %s', $object->getName());
}
protected function getObjectEditShortText($object) {
return pht('Edit Build Plan');
}
protected function getObjectCreateShortText() {
return pht('Create Build Plan');
}
protected function getEditorURI() {
return '/harbormaster/plan/edit/';
}
protected function getObjectCreateCancelURI($object) {
return '/harbormaster/plan/';
}
protected function getObjectViewURI($object) {
$id = $object->getID();
return "/harbormaster/plan/{$id}/";
}
protected function getCreateNewObjectPolicy() {
return $this->getApplication()->getPolicy(
HarbormasterCreatePlansCapability::CAPABILITY);
}
protected function buildCustomEditFields($object) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setIsRequired(true)
->setTransactionType(HarbormasterBuildPlanTransaction::TYPE_NAME)
->setDescription(pht('The build plan name.'))
->setConduitDescription(pht('Rename the plan.'))
->setConduitTypeDescription(pht('New plan name.'))
->setValue($object->getName()),
);
}
}

View file

@ -11,6 +11,10 @@ final class HarbormasterBuildPlanEditor
return pht('Harbormaster Build Plans');
}
protected function supportsSearch() {
return true;
}
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
$types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
@ -90,7 +94,7 @@ final class HarbormasterBuildPlanEditor
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('Plan name is required.'),
pht('You must choose a name for your build plan.'),
last($xactions));
$error->setIsMissingFieldError(true);

View file

@ -2,11 +2,23 @@
interface HarbormasterBuildableInterface {
/**
* Get the object PHID which best identifies this buildable to humans.
*
* This object is the primary object associated with the buildable in the
* UI. The most human-readable object for a buildable varies: for example,
* for diffs the container (the revision) is more meaningful than the
* buildable (the diff), but for commits the buildable (the commit) is more
* meaningful than the container (the repository).
*
* @return phid Related object PHID most meaningful for human viewers.
*/
public function getHarbormasterBuildableDisplayPHID();
public function getHarbormasterBuildablePHID();
public function getHarbormasterContainerPHID();
public function getBuildVariables();
public function getAvailableBuildVariables();
}

View file

@ -0,0 +1,150 @@
<?php
final class HarbormasterManagementArchiveLogsWorkflow
extends HarbormasterManagementWorkflow {
protected function didConstruct() {
$this
->setName('archive-logs')
->setExamples('**archive-logs** [__options__] --mode __mode__')
->setSynopsis(pht('Compress, decompress, store or destroy build logs.'))
->setArguments(
array(
array(
'name' => 'mode',
'param' => 'mode',
'help' => pht(
'Use "plain" to remove encoding, or "compress" to compress '.
'logs.'),
),
array(
'name' => 'details',
'help' => pht(
'Show more details about operations as they are performed. '.
'Slow! But also very reassuring!'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$viewer = $this->getViewer();
$mode = $args->getArg('mode');
if (!$mode) {
throw new PhutilArgumentUsageException(
pht('Choose an archival mode with --mode.'));
}
$valid_modes = array(
'plain',
'compress',
);
$valid_modes = array_fuse($valid_modes);
if (empty($valid_modes[$mode])) {
throw new PhutilArgumentUsageException(
pht(
'Unknown mode "%s". Valid modes are: %s.',
$mode,
implode(', ', $valid_modes)));
}
$log_table = new HarbormasterBuildLog();
$logs = new LiskMigrationIterator($log_table);
$show_details = $args->getArg('details');
if ($show_details) {
$total_old = 0;
$total_new = 0;
}
foreach ($logs as $log) {
echo tsprintf(
"%s\n",
pht('Processing Harbormaster build log #%d...', $log->getID()));
if ($show_details) {
$old_stats = $this->computeDetails($log);
}
switch ($mode) {
case 'plain':
$log->decompressLog();
break;
case 'compress':
$log->compressLog();
break;
}
if ($show_details) {
$new_stats = $this->computeDetails($log);
$this->printStats($old_stats, $new_stats);
$total_old += $old_stats['bytes'];
$total_new += $new_stats['bytes'];
}
}
if ($show_details) {
echo tsprintf(
"%s\n",
pht(
'Done. Total byte size of affected logs: %s -> %s.',
new PhutilNumber($total_old),
new PhutilNumber($total_new)));
}
return 0;
}
private function computeDetails(HarbormasterBuildLog $log) {
$bytes = 0;
$chunks = 0;
$hash = hash_init('sha1');
foreach ($log->newChunkIterator() as $chunk) {
$bytes += strlen($chunk->getChunk());
$chunks++;
hash_update($hash, $chunk->getChunkDisplayText());
}
return array(
'bytes' => $bytes,
'chunks' => $chunks,
'hash' => hash_final($hash),
);
}
private function printStats(array $old_stats, array $new_stats) {
echo tsprintf(
" %s\n",
pht(
'%s: %s -> %s',
pht('Stored Bytes'),
new PhutilNumber($old_stats['bytes']),
new PhutilNumber($new_stats['bytes'])));
echo tsprintf(
" %s\n",
pht(
'%s: %s -> %s',
pht('Stored Chunks'),
new PhutilNumber($old_stats['chunks']),
new PhutilNumber($new_stats['chunks'])));
echo tsprintf(
" %s\n",
pht(
'%s: %s -> %s',
pht('Data Hash'),
$old_stats['hash'],
$new_stats['hash']));
if ($old_stats['hash'] !== $new_stats['hash']) {
throw new Exception(
pht('Log data hashes differ! Something is tragically wrong!'));
}
}
}

View file

@ -21,8 +21,7 @@ final class HarbormasterBuildablePHIDType extends PhabricatorPHIDType {
array $phids) {
return id(new HarbormasterBuildableQuery())
->withPHIDs($phids)
->needBuildableHandles(true);
->withPHIDs($phids);
}
public function loadHandles(
@ -30,15 +29,30 @@ final class HarbormasterBuildablePHIDType extends PhabricatorPHIDType {
array $handles,
array $objects) {
$viewer = $query->getViewer();
$target_phids = array();
foreach ($objects as $phid => $object) {
$target_phids[] = $object->getBuildablePHID();
}
$target_handles = $viewer->loadHandles($target_phids);
foreach ($handles as $phid => $handle) {
$buildable = $objects[$phid];
$id = $buildable->getID();
$target = $buildable->getBuildableHandle()->getFullName();
$buildable_phid = $buildable->getBuildablePHID();
$handle->setURI("/B{$id}");
$handle->setName("B{$id}");
$handle->setFullName("B{$id}: ".$target);
$target = $target_handles[$buildable_phid];
$target_name = $target->getFullName();
$uri = $buildable->getURI();
$monogram = $buildable->getMonogram();
$handle
->setURI($uri)
->setName($monogram)
->setFullName("{$monogram}: {$target_name}");
}
}

View file

@ -35,6 +35,12 @@ final class HarbormasterBuildPlanQuery
return $this;
}
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new HarbormasterBuildPlanNameNgrams(),
$ngrams);
}
public function needBuildSteps($need) {
$this->needBuildSteps = $need;
return $this;
@ -74,41 +80,45 @@ final class HarbormasterBuildPlanQuery
if ($this->ids !== null) {
$where[] = qsprintf(
$conn,
'id IN (%Ld)',
'plan.id IN (%Ld)',
$this->ids);
}
if ($this->phids !== null) {
$where[] = qsprintf(
$conn,
'phid IN (%Ls)',
'plan.phid IN (%Ls)',
$this->phids);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'planStatus IN (%Ls)',
'plan.planStatus IN (%Ls)',
$this->statuses);
}
if (strlen($this->datasourceQuery)) {
$where[] = qsprintf(
$conn,
'name LIKE %>',
'plan.name LIKE %>',
$this->datasourceQuery);
}
if ($this->planAutoKeys !== null) {
$where[] = qsprintf(
$conn,
'planAutoKey IN (%Ls)',
'plan.planAutoKey IN (%Ls)',
$this->planAutoKeys);
}
return $where;
}
protected function getPrimaryTableAlias() {
return 'plan';
}
public function getQueryApplicationClass() {
return 'PhabricatorHarbormasterApplication';
}

View file

@ -17,6 +17,10 @@ final class HarbormasterBuildPlanSearchEngine
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains'))
->setKey('match')
->setDescription(pht('Search for namespaces by name substring.')),
id(new PhabricatorSearchCheckboxesField())
->setLabel(pht('Status'))
->setKey('status')
@ -32,6 +36,10 @@ final class HarbormasterBuildPlanSearchEngine
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
if ($map['match'] !== null) {
$query->withNameNgrams($map['match']);
}
if ($map['status']) {
$query->withStatuses($map['status']);
}
@ -76,12 +84,23 @@ final class HarbormasterBuildPlanSearchEngine
$viewer = $this->requireViewer();
if ($plans) {
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(mpull($plans, 'getPHID'))
->withEdgeTypes(
array(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
));
$edge_query->execute();
}
$list = new PHUIObjectItemListView();
foreach ($plans as $plan) {
$id = $plan->getID();
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Plan %d', $plan->getID()))
->setObjectName(pht('Plan %d', $id))
->setHeader($plan->getName());
if ($plan->isDisabled()) {
@ -94,6 +113,17 @@ final class HarbormasterBuildPlanSearchEngine
$item->setHref($this->getApplicationURI("plan/{$id}/"));
$phid = $plan->getPHID();
$project_phids = $edge_query->getDestinationPHIDs(array($phid));
$project_handles = $viewer->loadHandles($project_phids);
$item->addAttribute(
id(new PHUIHandleTagListView())
->setLimit(4)
->setNoDataString(pht('No Projects'))
->setSlim(true)
->setHandles($project_handles));
$list->addItem($item);
}

View file

@ -7,11 +7,10 @@ final class HarbormasterBuildableQuery
private $phids;
private $buildablePHIDs;
private $containerPHIDs;
private $statuses;
private $manualBuildables;
private $needContainerObjects;
private $needContainerHandles;
private $needBuildableHandles;
private $needBuilds;
private $needTargets;
@ -45,13 +44,8 @@ final class HarbormasterBuildableQuery
return $this;
}
public function needContainerHandles($need) {
$this->needContainerHandles = $need;
return $this;
}
public function needBuildableHandles($need) {
$this->needBuildableHandles = $need;
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
@ -99,11 +93,8 @@ final class HarbormasterBuildableQuery
}
protected function didFilterPage(array $page) {
if ($this->needContainerObjects || $this->needContainerHandles) {
$container_phids = array_filter(mpull($page, 'getContainerPHID'));
if ($this->needContainerObjects) {
$containers = array();
$container_phids = array_filter(mpull($page, 'getContainerPHID'));
if ($container_phids) {
$containers = id(new PhabricatorObjectQuery())
@ -112,6 +103,8 @@ final class HarbormasterBuildableQuery
->setParentQuery($this)
->execute();
$containers = mpull($containers, null, 'getPHID');
} else {
$containers = array();
}
foreach ($page as $key => $buildable) {
@ -120,42 +113,6 @@ final class HarbormasterBuildableQuery
}
}
if ($this->needContainerHandles) {
$handles = array();
if ($container_phids) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs($container_phids)
->setParentQuery($this)
->execute();
}
foreach ($page as $key => $buildable) {
$container_phid = $buildable->getContainerPHID();
$buildable->attachContainerHandle(idx($handles, $container_phid));
}
}
}
if ($this->needBuildableHandles) {
$handles = array();
$handle_phids = array_filter(mpull($page, 'getBuildablePHID'));
if ($handle_phids) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->getViewer())
->withPHIDs($handle_phids)
->setParentQuery($this)
->execute();
}
foreach ($page as $key => $buildable) {
$handle_phid = $buildable->getBuildablePHID();
$buildable->attachBuildableHandle(idx($handles, $handle_phid));
}
}
if ($this->needBuilds || $this->needTargets) {
$builds = id(new HarbormasterBuildQuery())
->setViewer($this->getViewer())
@ -203,6 +160,13 @@ final class HarbormasterBuildableQuery
$this->containerPHIDs);
}
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'buildableStatus in (%Ls)',
$this->statuses);
}
if ($this->manualBuildables !== null) {
$where[] = qsprintf(
$conn,

View file

@ -11,150 +11,89 @@ final class HarbormasterBuildableSearchEngine
return 'PhabricatorHarbormasterApplication';
}
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$saved = new PhabricatorSavedQuery();
public function newQuery() {
return new HarbormasterBuildableQuery();
}
$revisions = $this->readPHIDsFromRequest(
$request,
'revisions',
array(
DifferentialRevisionPHIDType::TYPECONST,
));
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchStringListField())
->setKey('objectPHIDs')
->setAliases(array('objects'))
->setLabel(pht('Objects'))
->setPlaceholder(pht('rXabcdef, PHID-DIFF-1234, ...'))
->setDescription(pht('Search for builds of particular objects.')),
id(new PhabricatorSearchStringListField())
->setKey('containerPHIDs')
->setAliases(array('containers'))
->setLabel(pht('Containers'))
->setPlaceholder(pht('rXYZ, R123, D456, ...'))
->setDescription(
pht('Search for builds by containing revision or repository.')),
id(new PhabricatorSearchCheckboxesField())
->setKey('statuses')
->setLabel(pht('Statuses'))
->setOptions(HarbormasterBuildable::getBuildStatusMap())
->setDescription(pht('Search for builds by buildable status.')),
id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Manual'))
->setKey('manual')
->setDescription(
pht('Search for only manual or automatic buildables.'))
->setOptions(
pht('(Show All)'),
pht('Show Only Manual Builds'),
pht('Show Only Automated Builds')),
);
}
$repositories = $this->readPHIDsFromRequest(
$request,
'repositories',
array(
PhabricatorRepositoryRepositoryPHIDType::TYPECONST,
));
private function resolvePHIDs(array $names) {
$viewer = $this->requireViewer();
$container_phids = array_merge($revisions, $repositories);
$saved->setParameter('containerPHIDs', $container_phids);
$commits = $this->readPHIDsFromRequest(
$request,
'commits',
array(
PhabricatorRepositoryCommitPHIDType::TYPECONST,
));
$diffs = $this->readListFromRequest($request, 'diffs');
if ($diffs) {
$diffs = id(new DifferentialDiffQuery())
->setViewer($this->requireViewer())
->withIDs($diffs)
$objects = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withNames($names)
->execute();
$diffs = mpull($diffs, 'getPHID', 'getPHID');
// TODO: Instead of using string lists, we should ideally be using some
// kind of smart field with resolver logic that can help users type the
// right stuff. For now, just return a bogus value here so nothing matches
// but the form doesn't explode.
if (!$objects) {
return array('-');
}
$buildable_phids = array_merge($commits, $diffs);
$saved->setParameter('buildablePHIDs', $buildable_phids);
$saved->setParameter(
'manual',
$this->readBoolFromRequest($request, 'manual'));
return $saved;
return mpull($objects, 'getPHID');
}
public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
$query = id(new HarbormasterBuildableQuery())
->needContainerHandles(true)
->needBuildableHandles(true);
protected function buildQueryFromParameters(array $map) {
$query = $this->newQuery();
$container_phids = $saved->getParameter('containerPHIDs', array());
if ($container_phids) {
$query->withContainerPHIDs($container_phids);
if ($map['objectPHIDs']) {
$phids = $this->resolvePHIDs($map['objectPHIDs']);
if ($phids) {
$query->withBuildablePHIDs($phids);
}
}
$buildable_phids = $saved->getParameter('buildablePHIDs', array());
if ($buildable_phids) {
$query->withBuildablePHIDs($buildable_phids);
if ($map['containerPHIDs']) {
$phids = $this->resolvePHIDs($map['containerPHIDs']);
if ($phids) {
$query->withContainerPHIDs($phids);
}
}
$manual = $saved->getParameter('manual');
if ($manual !== null) {
$query->withManualBuildables($manual);
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
if ($map['manual'] !== null) {
$query->withManualBuildables($map['manual']);
}
return $query;
}
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved_query) {
$container_phids = $saved_query->getParameter('containerPHIDs', array());
$buildable_phids = $saved_query->getParameter('buildablePHIDs', array());
$all_phids = array_merge($container_phids, $buildable_phids);
$revision_names = array();
$diff_names = array();
$repository_names = array();
$commit_names = array();
if ($all_phids) {
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->requireViewer())
->withPHIDs($all_phids)
->execute();
foreach ($all_phids as $phid) {
$object = idx($objects, $phid);
if (!$object) {
continue;
}
if ($object instanceof DifferentialRevision) {
$revision_names[] = 'D'.$object->getID();
} else if ($object instanceof DifferentialDiff) {
$diff_names[] = $object->getID();
} else if ($object instanceof PhabricatorRepository) {
$repository_names[] = $object->getMonogram();
} else if ($object instanceof PhabricatorRepositoryCommit) {
$repository = $object->getRepository();
$commit_names[] = $repository->formatCommitName(
$object->getCommitIdentifier());
}
}
}
$form
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Differential Revisions'))
->setName('revisions')
->setValue(implode(', ', $revision_names)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Differential Diffs'))
->setName('diffs')
->setValue(implode(', ', $diff_names)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Repositories'))
->setName('repositories')
->setValue(implode(', ', $repository_names)))
->appendChild(
id(new AphrontFormTextControl())
->setLabel(pht('Commits'))
->setName('commits')
->setValue(implode(', ', $commit_names)))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Origin'))
->setName('manual')
->setValue($this->getBoolFromQuery($saved_query, 'manual'))
->setOptions(
array(
'' => pht('(All Origins)'),
'true' => pht('Manual Buildables'),
'false' => pht('Automatic Buildables'),
)));
}
protected function getURI($path) {
return '/harbormaster/'.$path;
}
@ -185,35 +124,60 @@ final class HarbormasterBuildableSearchEngine
$viewer = $this->requireViewer();
$phids = array();
foreach ($buildables as $buildable) {
$phids[] = $buildable->getBuildableObject()
->getHarbormasterBuildableDisplayPHID();
$phids[] = $buildable->getContainerPHID();
$phids[] = $buildable->getBuildablePHID();
}
$handles = $viewer->loadHandles($phids);
$list = new PHUIObjectItemListView();
foreach ($buildables as $buildable) {
$id = $buildable->getID();
$display_phid = $buildable->getBuildableObject()
->getHarbormasterBuildableDisplayPHID();
$container_phid = $buildable->getContainerPHID();
$buildable_phid = $buildable->getBuildablePHID();
$item = id(new PHUIObjectItemView())
->setHeader(pht('Buildable %d', $buildable->getID()));
if ($buildable->getContainerHandle() !== null) {
$item->addAttribute($buildable->getContainerHandle()->getName());
}
if ($buildable->getBuildableHandle() !== null) {
$item->addAttribute($buildable->getBuildableHandle()->getFullName());
->setObjectName(pht('Buildable %d', $buildable->getID()));
if ($display_phid) {
$handle = $handles[$display_phid];
$item->setHeader($handle->getFullName());
}
if ($id) {
$item->setHref("/B{$id}");
if ($container_phid && ($container_phid != $display_phid)) {
$handle = $handles[$container_phid];
$item->addAttribute($handle->getName());
}
if ($buildable_phid && ($buildable_phid != $display_phid)) {
$handle = $handles[$buildable_phid];
$item->addAttribute($handle->getFullName());
}
$item->setHref($buildable->getURI());
if ($buildable->getIsManualBuildable()) {
$item->addIcon('fa-wrench grey', pht('Manual'));
}
$item->setStatusIcon('fa-wrench '.
HarbormasterBuildable::getBuildableStatusColor(
$buildable->getBuildableStatus()));
$item->addByline(HarbormasterBuildable::getBuildableStatusName(
$buildable->getBuildableStatus()));
$status = $buildable->getBuildableStatus();
$status_icon = HarbormasterBuildable::getBuildableStatusIcon($status);
$status_color = HarbormasterBuildable::getBuildableStatusColor($status);
$status_label = HarbormasterBuildable::getBuildableStatusName($status);
$item->setStatusIcon("{$status_icon} {$status_color}", $status_label);
$list->addItem($item);
}
$result = new PhabricatorApplicationSearchResultView();

View file

@ -31,24 +31,7 @@ final class HarbormasterWaitForPreviousBuildStepImplementation
// Block until all previous builds of the same build plan have
// finished.
$plan = $build->getBuildPlan();
$existing_logs = id(new HarbormasterBuildLogQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildTargetPHIDs(array($build_target->getPHID()))
->execute();
if ($existing_logs) {
$log = head($existing_logs);
} else {
$log = $build->createLog($build_target, 'waiting', 'blockers');
}
$blockers = $this->getBlockers($object, $plan, $build);
if ($blockers) {
$log->start();
$log->append(pht("Blocked by: %s\n", implode(',', $blockers)));
$log->finalize();
}
if ($blockers) {
throw new PhabricatorWorkerYieldException(15);

View file

@ -13,8 +13,6 @@ final class HarbormasterBuildable extends HarbormasterDAO
private $buildableObject = self::ATTACHABLE;
private $containerObject = self::ATTACHABLE;
private $buildableHandle = self::ATTACHABLE;
private $containerHandle = self::ATTACHABLE;
private $builds = self::ATTACHABLE;
const STATUS_BUILDING = 'building';
@ -22,16 +20,16 @@ final class HarbormasterBuildable extends HarbormasterDAO
const STATUS_FAILED = 'failed';
public static function getBuildableStatusName($status) {
switch ($status) {
case self::STATUS_BUILDING:
return pht('Building');
case self::STATUS_PASSED:
return pht('Passed');
case self::STATUS_FAILED:
return pht('Failed');
default:
return pht('Unknown');
$map = self::getBuildStatusMap();
return idx($map, $status, pht('Unknown ("%s")', $status));
}
public static function getBuildStatusMap() {
return array(
self::STATUS_BUILDING => pht('Building'),
self::STATUS_PASSED => pht('Passed'),
self::STATUS_FAILED => pht('Failed'),
);
}
public static function getBuildableStatusIcon($status) {
@ -70,6 +68,10 @@ final class HarbormasterBuildable extends HarbormasterDAO
return 'B'.$this->getID();
}
public function getURI() {
return '/'.$this->getMonogram();
}
/**
* Returns an existing buildable for the object's PHID or creates a
* new buildable implicitly if needed.
@ -237,24 +239,6 @@ final class HarbormasterBuildable extends HarbormasterDAO
return $this->assertAttached($this->containerObject);
}
public function attachContainerHandle($container_handle) {
$this->containerHandle = $container_handle;
return $this;
}
public function getContainerHandle() {
return $this->assertAttached($this->containerHandle);
}
public function attachBuildableHandle($buildable_handle) {
$this->buildableHandle = $buildable_handle;
return $this;
}
public function getBuildableHandle() {
return $this->assertAttached($this->buildableHandle);
}
public function attachBuilds(array $builds) {
assert_instances_of($builds, 'HarbormasterBuild');
$this->builds = $builds;
@ -318,6 +302,10 @@ final class HarbormasterBuildable extends HarbormasterDAO
/* -( HarbormasterBuildableInterface )------------------------------------- */
public function getHarbormasterBuildableDisplayPHID() {
return $this->getBuildableObject()->getHarbormasterBuildableDisplayPHID();
}
public function getHarbormasterBuildablePHID() {
// NOTE: This is essentially just for convenience, as it allows you create
// a copy of a buildable by specifying `B123` without bothering to go

View file

@ -21,31 +21,6 @@ final class HarbormasterSchemaSpec extends PhabricatorConfigSchemaSpec {
),
));
$this->buildRawSchema(
id(new HarbormasterBuildable())->getApplicationName(),
'harbormaster_buildlogchunk',
array(
'id' => 'auto',
'logID' => 'id',
'encoding' => 'text32',
// T6203/NULLABILITY
// Both the type and nullability of this column are crazily wrong.
'size' => 'uint32?',
'chunk' => 'bytes',
),
array(
'PRIMARY' => array(
'columns' => array('id'),
'unique' => true,
),
'key_log' => array(
'columns' => array('logID'),
),
));
}
}

View file

@ -234,23 +234,6 @@ final class HarbormasterBuild extends HarbormasterDAO
return ($this->getPlanAutoKey() !== null);
}
public function createLog(
HarbormasterBuildTarget $build_target,
$log_source,
$log_type) {
$log_source = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(250)
->truncateString($log_source);
$log = HarbormasterBuildLog::initializeNewBuildLog($build_target)
->setLogSource($log_source)
->setLogType($log_type)
->save();
return $log;
}
public function retrieveVariablesFromBuild() {
$results = array(
'buildable.diff' => null,
@ -323,6 +306,11 @@ final class HarbormasterBuild extends HarbormasterDAO
return ($this->getBuildStatus() == self::STATUS_PAUSED);
}
public function getURI() {
$id = $this->getID();
return "/harbormaster/build/{$id}/";
}
/* -( Build Commands )----------------------------------------------------- */

View file

@ -1,6 +1,7 @@
<?php
final class HarbormasterBuildLog extends HarbormasterDAO
final class HarbormasterBuildLog
extends HarbormasterDAO
implements PhabricatorPolicyInterface {
protected $buildTargetPHID;
@ -10,18 +11,18 @@ final class HarbormasterBuildLog extends HarbormasterDAO
protected $live;
private $buildTarget = self::ATTACHABLE;
private $start;
private $rope;
private $isOpen;
const CHUNK_BYTE_LIMIT = 102400;
/**
* The log is encoded as plain text.
*/
const ENCODING_TEXT = 'text';
public function __construct() {
$this->rope = new PhutilRope();
}
public function __destruct() {
if ($this->start) {
$this->finalize($this->start);
if ($this->isOpen) {
$this->closeBuildLog();
}
}
@ -34,6 +35,37 @@ final class HarbormasterBuildLog extends HarbormasterDAO
->setLive(0);
}
public function openBuildLog() {
if ($this->isOpen) {
throw new Exception(pht('This build log is already open!'));
}
$this->isOpen = true;
return $this
->setLive(1)
->save();
}
public function closeBuildLog() {
if (!$this->isOpen) {
throw new Exception(pht('This build log is not open!'));
}
if ($this->canCompressLog()) {
$this->compressLog();
}
$start = $this->getDateCreated();
$now = PhabricatorTime::getNow();
return $this
->setDuration($now - $start)
->setLive(0)
->save();
}
protected function getConfiguration() {
return array(
self::CONFIG_AUX_PHID => true,
@ -73,122 +105,174 @@ final class HarbormasterBuildLog extends HarbormasterDAO
return pht('Build Log');
}
public function start() {
if ($this->getLive()) {
throw new Exception(
pht('Live logging has already started for this log.'));
}
$this->setLive(1);
$this->save();
$this->start = PhabricatorTime::getNow();
return time();
}
public function append($content) {
if (!$this->getLive()) {
throw new Exception(
pht('Start logging before appending data to the log.'));
}
if (strlen($content) === 0) {
return;
throw new PhutilInvalidStateException('openBuildLog');
}
// If the length of the content is greater than the chunk size limit,
// then we can never fit the content in a single record. We need to
// split our content out and call append on it for as many parts as there
// are to the content.
if (strlen($content) > self::CHUNK_BYTE_LIMIT) {
$current = $content;
while (strlen($current) > self::CHUNK_BYTE_LIMIT) {
$part = substr($current, 0, self::CHUNK_BYTE_LIMIT);
$current = substr($current, self::CHUNK_BYTE_LIMIT);
$this->append($part);
}
$this->append($current);
return;
$content = (string)$content;
$this->rope->append($content);
$this->flush();
}
// Retrieve the size of last chunk from the DB for this log. If the
// chunk is over 500K, then we need to create a new log entry.
$conn = $this->establishConnection('w');
$result = queryfx_all(
$conn,
'SELECT id, size, encoding '.
'FROM harbormaster_buildlogchunk '.
'WHERE logID = %d '.
'ORDER BY id DESC '.
'LIMIT 1',
$this->getID());
if (count($result) === 0 ||
$result[0]['size'] + strlen($content) > self::CHUNK_BYTE_LIMIT ||
$result[0]['encoding'] !== self::ENCODING_TEXT) {
private function flush() {
// We must insert a new chunk because the data we are appending
// won't fit into the existing one, or we don't have any existing
// chunk data.
queryfx(
$conn,
'INSERT INTO harbormaster_buildlogchunk '.
'(logID, encoding, size, chunk) '.
'VALUES '.
'(%d, %s, %d, %B)',
$this->getID(),
self::ENCODING_TEXT,
strlen($content),
$content);
// TODO: Maybe don't flush more than a couple of times per second. If a
// caller writes a single character over and over again, we'll currently
// spend a lot of time flushing that.
$chunk_table = id(new HarbormasterBuildLogChunk())->getTableName();
$chunk_limit = self::CHUNK_BYTE_LIMIT;
$encoding_text = HarbormasterBuildLogChunk::CHUNK_ENCODING_TEXT;
$rope = $this->rope;
while (true) {
$length = $rope->getByteLength();
if (!$length) {
break;
}
$conn_w = $this->establishConnection('w');
$last = $this->loadLastChunkInfo();
$can_append =
($last) &&
($last['encoding'] == $encoding_text) &&
($last['size'] < $chunk_limit);
if ($can_append) {
$append_id = $last['id'];
$prefix_size = $last['size'];
} else {
// We have a resulting record that we can append our content onto.
$append_id = null;
$prefix_size = 0;
}
$data_limit = ($chunk_limit - $prefix_size);
$append_data = $rope->getPrefixBytes($data_limit);
$data_size = strlen($append_data);
if ($append_id) {
queryfx(
$conn,
'UPDATE harbormaster_buildlogchunk '.
'SET chunk = CONCAT(chunk, %B), size = LENGTH(CONCAT(chunk, %B))'.
'WHERE id = %d',
$content,
$content,
$result[0]['id']);
$conn_w,
'UPDATE %T SET chunk = CONCAT(chunk, %B), size = %d WHERE id = %d',
$chunk_table,
$append_data,
$prefix_size + $data_size,
$append_id);
} else {
$this->writeChunk($encoding_text, $data_size, $append_data);
}
$rope->removeBytesFromHead($data_size);
}
}
public function finalize($start = 0) {
if (!$this->getLive()) {
// TODO: Clean up this API.
return;
public function newChunkIterator() {
return id(new HarbormasterBuildLogChunkIterator($this))
->setPageSize(32);
}
// TODO: Encode the log contents in a gzipped format.
$this->reload();
if ($start > 0) {
$this->setDuration(time() - $start);
}
$this->setLive(0);
$this->save();
private function loadLastChunkInfo() {
$chunk_table = new HarbormasterBuildLogChunk();
$conn_w = $chunk_table->establishConnection('w');
return queryfx_one(
$conn_w,
'SELECT id, size, encoding FROM %T WHERE logID = %d
ORDER BY id DESC LIMIT 1',
$chunk_table->getTableName(),
$this->getID());
}
public function getLogText() {
// TODO: This won't cope very well if we're pulling like a 700MB
// log file out of the DB. We should probably implement some sort
// of optional limit parameter so that when we're rendering out only
// 25 lines in the UI, we don't wastefully read in the whole log.
// TODO: Remove this method since it won't scale for big logs.
// We have to read our content out of the database and stitch all of
// the log data back together.
$conn = $this->establishConnection('r');
$result = queryfx_all(
$conn,
'SELECT chunk '.
'FROM harbormaster_buildlogchunk '.
'WHERE logID = %d '.
'ORDER BY id ASC',
$this->getID());
$all_chunks = $this->newChunkIterator();
$content = '';
foreach ($result as $row) {
$content .= $row['chunk'];
$full_text = array();
foreach ($all_chunks as $chunk) {
$full_text[] = $chunk->getChunkDisplayText();
}
return $content;
return implode('', $full_text);
}
private function canCompressLog() {
return function_exists('gzdeflate');
}
public function compressLog() {
$this->processLog(HarbormasterBuildLogChunk::CHUNK_ENCODING_GZIP);
}
public function decompressLog() {
$this->processLog(HarbormasterBuildLogChunk::CHUNK_ENCODING_TEXT);
}
private function processLog($mode) {
$chunks = $this->newChunkIterator();
// NOTE: Because we're going to insert new chunks, we need to stop the
// iterator once it hits the final chunk which currently exists. Otherwise,
// it may start consuming chunks we just wrote and run forever.
$last = $this->loadLastChunkInfo();
if ($last) {
$chunks->setRange(null, $last['id']);
}
$byte_limit = self::CHUNK_BYTE_LIMIT;
$rope = new PhutilRope();
$this->openTransaction();
foreach ($chunks as $chunk) {
$rope->append($chunk->getChunkDisplayText());
$chunk->delete();
while ($rope->getByteLength() > $byte_limit) {
$this->writeEncodedChunk($rope, $byte_limit, $mode);
}
}
while ($rope->getByteLength()) {
$this->writeEncodedChunk($rope, $byte_limit, $mode);
}
$this->saveTransaction();
}
private function writeEncodedChunk(PhutilRope $rope, $length, $mode) {
$data = $rope->getPrefixBytes($length);
$size = strlen($data);
switch ($mode) {
case HarbormasterBuildLogChunk::CHUNK_ENCODING_TEXT:
// Do nothing.
break;
case HarbormasterBuildLogChunk::CHUNK_ENCODING_GZIP:
$data = gzdeflate($data);
if ($data === false) {
throw new Exception(pht('Failed to gzdeflate() log data!'));
}
break;
default:
throw new Exception(pht('Unknown chunk encoding "%s"!', $mode));
}
$this->writeChunk($mode, $size, $data);
$rope->removeBytesFromHead($size);
}
private function writeChunk($encoding, $raw_size, $data) {
return id(new HarbormasterBuildLogChunk())
->setLogID($this->getID())
->setEncoding($encoding)
->setSize($raw_size)
->setChunk($data)
->save();
}

View file

@ -0,0 +1,61 @@
<?php
final class HarbormasterBuildLogChunk
extends HarbormasterDAO {
protected $logID;
protected $encoding;
protected $size;
protected $chunk;
const CHUNK_ENCODING_TEXT = 'text';
const CHUNK_ENCODING_GZIP = 'gzip';
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_BINARY => array(
'chunk' => true,
),
self::CONFIG_COLUMN_SCHEMA => array(
'logID' => 'id',
'encoding' => 'text32',
// T6203/NULLABILITY
// Both the type and nullability of this column are crazily wrong.
'size' => 'uint32?',
'chunk' => 'bytes',
),
self::CONFIG_KEY_SCHEMA => array(
'key_log' => array(
'columns' => array('logID'),
),
),
) + parent::getConfiguration();
}
public function getChunkDisplayText() {
$data = $this->getChunk();
$encoding = $this->getEncoding();
switch ($encoding) {
case self::CHUNK_ENCODING_TEXT:
// Do nothing, data is already plaintext.
break;
case self::CHUNK_ENCODING_GZIP:
$data = gzinflate($data);
if ($data === false) {
throw new Exception(pht('Unable to inflate log chunk!'));
}
break;
default:
throw new Exception(
pht('Unknown log chunk encoding ("%s")!', $encoding));
}
return $data;
}
}

View file

@ -0,0 +1,49 @@
<?php
final class HarbormasterBuildLogChunkIterator
extends PhutilBufferedIterator {
private $log;
private $cursor;
private $min = 0;
private $max = PHP_INT_MAX;
public function __construct(HarbormasterBuildLog $log) {
$this->log = $log;
}
protected function didRewind() {
$this->cursor = $this->min;
}
public function key() {
return $this->current()->getID();
}
public function setRange($min, $max) {
$this->min = (int)$min;
$this->max = (int)$max;
return $this;
}
protected function loadPage() {
if ($this->cursor > $this->max) {
return array();
}
$results = id(new HarbormasterBuildLogChunk())->loadAllWhere(
'logID = %d AND id >= %d AND id <= %d ORDER BY id ASC LIMIT %d',
$this->log->getID(),
$this->cursor,
$this->max,
$this->getPageSize());
if ($results) {
$this->cursor = last($results)->getID() + 1;
}
return $results;
}
}

View file

@ -256,9 +256,8 @@ final class HarbormasterBuildTarget extends HarbormasterDAO
$log = HarbormasterBuildLog::initializeNewBuildLog($this)
->setLogSource($log_source)
->setLogType($log_type);
$log->start();
->setLogType($log_type)
->openBuildLog();
return $log;
}

View file

@ -61,6 +61,11 @@ final class HarbormasterBuildUnitMessage
'description' => pht(
'Coverage information for this test.'),
),
'details' => array(
'type' => 'optional string',
'description' => pht(
'Additional human-readable information about the failure.'),
),
);
}
@ -94,6 +99,11 @@ final class HarbormasterBuildUnitMessage
$obj->setProperty('coverage', $coverage);
}
$details = idx($dict, 'details');
if ($details) {
$obj->setProperty('details', $details);
}
return $obj;
}
@ -135,19 +145,36 @@ final class HarbormasterBuildUnitMessage
return $this;
}
public function getSortKey() {
// TODO: Maybe use more numeric values after T6861.
$map = array(
ArcanistUnitTestResult::RESULT_FAIL => 'A',
ArcanistUnitTestResult::RESULT_BROKEN => 'B',
ArcanistUnitTestResult::RESULT_UNSOUND => 'C',
ArcanistUnitTestResult::RESULT_PASS => 'Z',
);
public function getUnitMessageDetails() {
return $this->getProperty('details', '');
}
$result = idx($map, $this->getResult(), 'N');
public function getUnitMessageDisplayName() {
$name = $this->getName();
$namespace = $this->getNamespace();
if (strlen($namespace)) {
$name = $namespace.'::'.$name;
}
$engine = $this->getEngine();
if (strlen($engine)) {
$name = $engine.' > '.$name;
}
if (!strlen($name)) {
return pht('Nameless Test (%d)', $this->getID());
}
return $name;
}
public function getSortKey() {
$status = $this->getResult();
$sort = HarbormasterUnitStatus::getUnitStatusSort($status);
$parts = array(
$result,
$sort,
$this->getEngine(),
$this->getNamespace(),
$this->getName(),

View file

@ -7,7 +7,9 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
implements
PhabricatorApplicationTransactionInterface,
PhabricatorPolicyInterface,
PhabricatorSubscribableInterface {
PhabricatorSubscribableInterface,
PhabricatorNgramsInterface,
PhabricatorProjectInterface {
protected $name;
protected $planStatus;
@ -198,4 +200,15 @@ final class HarbormasterBuildPlan extends HarbormasterDAO
return $messages;
}
/* -( PhabricatorNgramInterface )------------------------------------------ */
public function newNgrams() {
return array(
id(new HarbormasterBuildPlanNameNgrams())
->setValue($this->getName()),
);
}
}

View file

@ -0,0 +1,18 @@
<?php
final class HarbormasterBuildPlanNameNgrams
extends PhabricatorSearchNgrams {
public function getNgramKey() {
return 'buildplanname';
}
public function getColumnName() {
return 'name';
}
public function getApplicationName() {
return 'harbormaster';
}
}

View file

@ -5,6 +5,8 @@ final class HarbormasterUnitPropertyView extends AphrontView {
private $pathURIMap = array();
private $unitMessages = array();
private $limit;
private $fullResultsURI;
private $notice;
public function setPathURIMap(array $map) {
$this->pathURIMap = $map;
@ -22,18 +24,47 @@ final class HarbormasterUnitPropertyView extends AphrontView {
return $this;
}
public function setFullResultsURI($full_results_uri) {
$this->fullResultsURI = $full_results_uri;
return $this;
}
public function setNotice($notice) {
$this->notice = $notice;
return $this;
}
public function render() {
require_celerity_resource('harbormaster-css');
$messages = $this->unitMessages;
$messages = msort($messages, 'getSortKey');
$limit = $this->limit;
if ($this->limit) {
$messages = array_slice($messages, 0, $this->limit);
$display_messages = array_slice($messages, 0, $limit);
} else {
$display_messages = $messages;
}
$rows = array();
$any_duration = false;
foreach ($messages as $message) {
$result = $this->renderResult($message->getResult());
foreach ($display_messages as $message) {
$status = $message->getResult();
$icon_icon = HarbormasterUnitStatus::getUnitStatusIcon($status);
$icon_color = HarbormasterUnitStatus::getUnitStatusColor($status);
$icon_label = HarbormasterUnitStatus::getUnitStatusLabel($status);
$result_icon = id(new PHUIIconView())
->setIcon("{$icon_icon} {$icon_color}")
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $icon_label,
));
$duration = $message->getDuration();
if ($duration !== null) {
@ -41,37 +72,78 @@ final class HarbormasterUnitPropertyView extends AphrontView {
$duration = pht('%s ms', new PhutilNumber((int)(1000 * $duration)));
}
$name = $message->getName();
$name = $message->getUnitMessageDisplayName();
$id = $message->getID();
$namespace = $message->getNamespace();
if (strlen($namespace)) {
$name = $namespace.'::'.$name;
if ($id) {
$name = phutil_tag(
'a',
array(
'href' => "/harbormaster/unit/view/{$id}/",
),
$name);
}
$engine = $message->getEngine();
if (strlen($engine)) {
$name = $engine.' > '.$name;
$details = $message->getUnitMessageDetails();
if (strlen($details)) {
$name = array(
$name,
$this->renderUnitTestDetails($details),
);
}
$rows[] = array(
$result,
$result_icon,
$duration,
$name,
);
}
$full_uri = $this->fullResultsURI;
if ($full_uri && (count($messages) > $limit)) {
$counts = array();
$groups = mgroup($messages, 'getResult');
foreach ($groups as $status => $group) {
$counts[] = HarbormasterUnitStatus::getUnitStatusCountLabel(
$status,
count($group));
}
$link_text = pht(
'View Full Test Results (%s)',
implode(" \xC2\xB7 ", $counts));
$full_link = phutil_tag(
'a',
array(
'href' => $full_uri,
),
$link_text);
$link_icon = id(new PHUIIconView())
->setIcon('fa-ellipsis-h lightgreytext');
$rows[] = array($link_icon, null, $full_link);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Result'),
null,
pht('Time'),
pht('Test'),
))
->setColumnClasses(
array(
null,
null,
'pri wide',
'top center',
'top right',
'top wide',
))
->setColumnWidths(
array(
'32px',
'64px',
))
->setColumnVisibility(
array(
@ -79,22 +151,32 @@ final class HarbormasterUnitPropertyView extends AphrontView {
$any_duration,
));
if ($this->notice) {
$table->setNotice($this->notice);
}
return $table;
}
private function renderResult($result) {
$names = array(
ArcanistUnitTestResult::RESULT_BROKEN => pht('Broken'),
ArcanistUnitTestResult::RESULT_FAIL => pht('Failed'),
ArcanistUnitTestResult::RESULT_UNSOUND => pht('Unsound'),
ArcanistUnitTestResult::RESULT_SKIP => pht('Skipped'),
ArcanistUnitTestResult::RESULT_PASS => pht('Passed'),
);
$result = idx($names, $result, $result);
private function renderUnitTestDetails($full_details) {
$details = id(new PhutilUTF8StringTruncator())
->setMaximumBytes(2048)
->truncateString($full_details);
$details = phutil_split_lines($details);
// TODO: Add some color.
$limit = 3;
if (count($details) > $limit) {
$details = array_slice($details, 0, $limit);
}
return $result;
$details = implode('', $details);
return phutil_tag(
'div',
array(
'class' => 'PhabricatorMonospaced harbormaster-unit-details',
),
$details);
}
}

View file

@ -0,0 +1,104 @@
<?php
final class HarbormasterUnitSummaryView extends AphrontView {
private $buildable;
private $messages;
private $limit;
private $excuse;
private $showViewAll;
public function setBuildable(HarbormasterBuildable $buildable) {
$this->buildable = $buildable;
return $this;
}
public function setUnitMessages(array $messages) {
$this->messages = $messages;
return $this;
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setExcuse($excuse) {
$this->excuse = $excuse;
return $this;
}
public function setShowViewAll($show_view_all) {
$this->showViewAll = $show_view_all;
return $this;
}
public function render() {
$messages = $this->messages;
$buildable = $this->buildable;
$id = $buildable->getID();
$full_uri = "/harbormaster/unit/{$id}/";
$messages = msort($messages, 'getSortKey');
$head_unit = head($messages);
if ($head_unit) {
$status = $head_unit->getResult();
$tag_text = HarbormasterUnitStatus::getUnitStatusLabel($status);
$tag_color = HarbormasterUnitStatus::getUnitStatusColor($status);
$tag_icon = HarbormasterUnitStatus::getUnitStatusIcon($status);
} else {
$tag_text = pht('No Unit Tests');
$tag_color = 'grey';
$tag_icon = 'fa-ban';
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Unit Tests'))
->setStatus($tag_icon, $tag_color, $tag_text);
if ($this->showViewAll) {
$view_all = id(new PHUIButtonView())
->setTag('a')
->setHref($full_uri)
->setIcon('fa-list-ul')
->setText('View All');
$header->addActionLink($view_all);
}
$box = id(new PHUIObjectBoxView())
->setHeader($header);
$table = id(new HarbormasterUnitPropertyView())
->setUnitMessages($messages);
if ($this->showViewAll) {
$table->setFullResultsURI($full_uri);
}
if ($this->limit) {
$table->setLimit($this->limit);
}
$excuse = $this->excuse;
if (strlen($excuse)) {
$excuse_icon = id(new PHUIIconView())
->setIcon('fa-commenting-o red');
$table->setNotice(
array(
$excuse_icon,
' ',
phutil_tag('strong', array(), pht('Excuse:')),
' ',
$excuse,
));
}
$box->setTable($table);
return $box;
}
}

View file

@ -90,13 +90,10 @@ final class HarbormasterTargetWorker extends HarbormasterWorker {
$target->setDateCompleted(PhabricatorTime::getNow());
$target->save();
} catch (Exception $ex) {
phlog($ex);
try {
$log = $build->createLog($target, 'core', 'exception');
$start = $log->start();
$log->append((string)$ex);
$log->finalize($start);
$log = $target->newLog('core', 'exception')
->append($ex)
->closeBuildLog();
} catch (Exception $log_ex) {
phlog($log_ex);
}

Some files were not shown because too many files have changed in this diff Show more