diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 44383032b7..be8d5b9d1c 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -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', diff --git a/resources/sql/autopatches/20160227.harbormaster.1.plann.sql b/resources/sql/autopatches/20160227.harbormaster.1.plann.sql new file mode 100644 index 0000000000..4c0b4f48b3 --- /dev/null +++ b/resources/sql/autopatches/20160227.harbormaster.1.plann.sql @@ -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}; diff --git a/resources/sql/autopatches/20160227.harbormaster.2.plani.php b/resources/sql/autopatches/20160227.harbormaster.2.plani.php new file mode 100644 index 0000000000..6dea004c06 --- /dev/null +++ b/resources/sql/autopatches/20160227.harbormaster.2.plani.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20160303.drydock.1.bluen.sql b/resources/sql/autopatches/20160303.drydock.1.bluen.sql new file mode 100644 index 0000000000..c0cbf90492 --- /dev/null +++ b/resources/sql/autopatches/20160303.drydock.1.bluen.sql @@ -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}; diff --git a/resources/sql/autopatches/20160303.drydock.2.bluei.php b/resources/sql/autopatches/20160303.drydock.2.bluei.php new file mode 100644 index 0000000000..c0b68c2262 --- /dev/null +++ b/resources/sql/autopatches/20160303.drydock.2.bluei.php @@ -0,0 +1,11 @@ +getPHID(), + array( + 'force' => true, + )); +} diff --git a/resources/sql/autopatches/20160303.drydock.3.edge.sql b/resources/sql/autopatches/20160303.drydock.3.edge.sql new file mode 100644 index 0000000000..2de8506ba9 --- /dev/null +++ b/resources/sql/autopatches/20160303.drydock.3.edge.sql @@ -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}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8528aa9a19..0a5b5e7cd9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/applications/almanac/application/PhabricatorAlmanacApplication.php b/src/applications/almanac/application/PhabricatorAlmanacApplication.php index 08b9c5fbc8..76caa3a06b 100644 --- a/src/applications/almanac/application/PhabricatorAlmanacApplication.php +++ b/src/applications/almanac/application/PhabricatorAlmanacApplication.php @@ -35,22 +35,18 @@ final class PhabricatorAlmanacApplication extends PhabricatorApplication { ); } - public function isPrototype() { - return true; - } - public function getRoutes() { return array( '/almanac/' => array( '' => 'AlmanacConsoleController', '(?Pservice)/' => array( $this->getQueryRoutePattern() => 'AlmanacServiceListController', - 'edit/(?:(?P\d+)/)?' => 'AlmanacServiceEditController', + $this->getEditRoutePattern('edit/') => 'AlmanacServiceEditController', 'view/(?P[^/]+)/' => 'AlmanacServiceViewController', ), '(?Pdevice)/' => array( $this->getQueryRoutePattern() => 'AlmanacDeviceListController', - 'edit/(?:(?P\d+)/)?' => 'AlmanacDeviceEditController', + $this->getEditRoutePattern('edit/') => 'AlmanacDeviceEditController', 'view/(?P[^/]+)/' => 'AlmanacDeviceViewController', ), 'interface/' => array( diff --git a/src/applications/almanac/controller/AlmanacController.php b/src/applications/almanac/controller/AlmanacController.php index 3b354b0803..c76fdf11b5 100644 --- a/src/applications/almanac/controller/AlmanacController.php +++ b/src/applications/almanac/controller/AlmanacController.php @@ -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) { diff --git a/src/applications/almanac/controller/AlmanacDeviceController.php b/src/applications/almanac/controller/AlmanacDeviceController.php index cdcb90c63a..442a0797e3 100644 --- a/src/applications/almanac/controller/AlmanacDeviceController.php +++ b/src/applications/almanac/controller/AlmanacDeviceController.php @@ -2,6 +2,11 @@ abstract class AlmanacDeviceController extends AlmanacController { + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new AlmanacDeviceSearchEngine()); + } + protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); diff --git a/src/applications/almanac/controller/AlmanacDeviceEditController.php b/src/applications/almanac/controller/AlmanacDeviceEditController.php index 3bbc388838..f8674a8a04 100644 --- a/src/applications/almanac/controller/AlmanacDeviceEditController.php +++ b/src/applications/almanac/controller/AlmanacDeviceEditController.php @@ -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(); } } diff --git a/src/applications/almanac/controller/AlmanacDeviceListController.php b/src/applications/almanac/controller/AlmanacDeviceListController.php index 99ede8a181..6ab486ee8e 100644 --- a/src/applications/almanac/controller/AlmanacDeviceListController.php +++ b/src/applications/almanac/controller/AlmanacDeviceListController.php @@ -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; - } - - } diff --git a/src/applications/almanac/controller/AlmanacDeviceViewController.php b/src/applications/almanac/controller/AlmanacDeviceViewController.php index 4836198763..f6bf697e31 100644 --- a/src/applications/almanac/controller/AlmanacDeviceViewController.php +++ b/src/applications/almanac/controller/AlmanacDeviceViewController.php @@ -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,24 +46,33 @@ 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, + )); } private function buildPropertyList(AlmanacDevice $device) { @@ -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); } diff --git a/src/applications/almanac/controller/AlmanacNamespaceViewController.php b/src/applications/almanac/controller/AlmanacNamespaceViewController.php index 4f140683a2..b999f219fc 100644 --- a/src/applications/almanac/controller/AlmanacNamespaceViewController.php +++ b/src/applications/almanac/controller/AlmanacNamespaceViewController.php @@ -21,42 +21,49 @@ 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, + )); } private function buildPropertyList(AlmanacNamespace $namespace) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer); + ->setUser($viewer) + ->setObject($namespace); + + $properties->invokeWillRenderEvent(); return $properties; } diff --git a/src/applications/almanac/controller/AlmanacNetworkViewController.php b/src/applications/almanac/controller/AlmanacNetworkViewController.php index 70bd0cb87e..11e6eaf799 100644 --- a/src/applications/almanac/controller/AlmanacNetworkViewController.php +++ b/src/applications/almanac/controller/AlmanacNetworkViewController.php @@ -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; } diff --git a/src/applications/almanac/controller/AlmanacServiceController.php b/src/applications/almanac/controller/AlmanacServiceController.php index 12bfbf94ea..b77e8e3853 100644 --- a/src/applications/almanac/controller/AlmanacServiceController.php +++ b/src/applications/almanac/controller/AlmanacServiceController.php @@ -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}/"; diff --git a/src/applications/almanac/controller/AlmanacServiceEditController.php b/src/applications/almanac/controller/AlmanacServiceEditController.php index 2b6f5a9f5d..b09deaa95d 100644 --- a/src/applications/almanac/controller/AlmanacServiceEditController.php +++ b/src/applications/almanac/controller/AlmanacServiceEditController.php @@ -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); - } - - $is_new = true; - - $title = pht('Create Service'); - $save_button = pht('Create Service'); + $engine + ->addContextParameter('serviceType', $service_type) + ->setServiceType($service_type); } - $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); } } diff --git a/src/applications/almanac/controller/AlmanacServiceListController.php b/src/applications/almanac/controller/AlmanacServiceListController.php index e09a199fb4..a46452beb1 100644 --- a/src/applications/almanac/controller/AlmanacServiceListController.php +++ b/src/applications/almanac/controller/AlmanacServiceListController.php @@ -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; - } - - } diff --git a/src/applications/almanac/controller/AlmanacServiceViewController.php b/src/applications/almanac/controller/AlmanacServiceViewController.php index 68defec4b9..0b59a47442 100644 --- a/src/applications/almanac/controller/AlmanacServiceViewController.php +++ b/src/applications/almanac/controller/AlmanacServiceViewController.php @@ -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); } diff --git a/src/applications/almanac/editor/AlmanacDeviceEditEngine.php b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php new file mode 100644 index 0000000000..f8ace15caf --- /dev/null +++ b/src/applications/almanac/editor/AlmanacDeviceEditEngine.php @@ -0,0 +1,85 @@ +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()), + ); + } + +} diff --git a/src/applications/almanac/editor/AlmanacServiceEditEngine.php b/src/applications/almanac/editor/AlmanacServiceEditEngine.php new file mode 100644 index 0000000000..00e4e076de --- /dev/null +++ b/src/applications/almanac/editor/AlmanacServiceEditEngine.php @@ -0,0 +1,97 @@ +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()), + ); + } + +} diff --git a/src/applications/almanac/editor/AlmanacServiceEditor.php b/src/applications/almanac/editor/AlmanacServiceEditor.php index 1f6fe09cfa..8b0326f749 100644 --- a/src/applications/almanac/editor/AlmanacServiceEditor.php +++ b/src/applications/almanac/editor/AlmanacServiceEditor.php @@ -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; + } + } diff --git a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php index 872b715e5b..70e3d8f029 100644 --- a/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesEditRecipientsController.php @@ -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()) - ->addCancelButton($view_uri) - ->setValue(pht('Add Recipients'))); - $form_box = id(new PHUIObjectBoxView()) - ->setHeaderText($title) - ->setForm($form); + ->setDatasource(new PhabricatorPeopleDatasource())); } - $recipient_list = id(new PhabricatorBadgesRecipientsListView()) - ->setBadge($badge) - ->setHandles($handles) - ->setUser($viewer); + $dialog = id(new AphrontDialogView()) + ->setUser($viewer) + ->setTitle(pht('Award Badges')) + ->appendForm($form) + ->addCancelButton($view_uri) + ->addSubmitButton(pht('Add Recipients')); - $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; } } diff --git a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php index 0282854790..bc467b703d 100644 --- a/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php +++ b/src/applications/badges/controller/PhabricatorBadgesRemoveRecipientsController.php @@ -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; diff --git a/src/applications/badges/controller/PhabricatorBadgesViewController.php b/src/applications/badges/controller/PhabricatorBadgesViewController.php index 81241edd9f..3858965d41 100644 --- a/src/applications/badges/controller/PhabricatorBadgesViewController.php +++ b/src/applications/badges/controller/PhabricatorBadgesViewController.php @@ -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; diff --git a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php index 30d883bb57..6b633bed5b 100644 --- a/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php +++ b/src/applications/badges/view/PhabricatorBadgesRecipientsListView.php @@ -1,6 +1,6 @@ user; diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php index d494fa7a0b..8d13f85e61 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventViewController.php @@ -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( - array( - $crumbs, - $box, - $timeline, - $add_comment_form, - ), - array( - 'title' => $page_title, - 'pageObjects' => array($event->getPHID()), + $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( + $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; + } + } diff --git a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php index 4fdeaeccb1..13cef002cf 100644 --- a/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php +++ b/src/applications/celerity/postprocessor/CelerityDefaultPostprocessor.php @@ -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)', diff --git a/src/applications/countdown/controller/PhabricatorCountdownViewController.php b/src/applications/countdown/controller/PhabricatorCountdownViewController.php index 401e159c5f..9a983f0ed9 100644 --- a/src/applications/countdown/controller/PhabricatorCountdownViewController.php +++ b/src/applications/countdown/controller/PhabricatorCountdownViewController.php @@ -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,34 +119,40 @@ 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())); - - $view->invokeWillRenderEvent(); - - $description = $countdown->getDescription(); - if (strlen($description)) { - $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - + ->setObject($countdown); + $view->invokeWillRenderEvent(); 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) { $viewer = $this->getViewer(); diff --git a/src/applications/countdown/view/PhabricatorCountdownView.php b/src/applications/countdown/view/PhabricatorCountdownView.php index 2e8f1dedb3..4e975c3626 100644 --- a/src/applications/countdown/view/PhabricatorCountdownView.php +++ b/src/applications/countdown/view/PhabricatorCountdownView.php @@ -1,46 +1,31 @@ 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', + $header_text = array( + 'C'.$countdown->getID(), + ' ', + phutil_tag( + 'a', array( - 'class' => 'phabricator-timer-header', + 'href' => '/countdown/'.$countdown->getID(), ), - array( - 'C'.$countdown->getID(), - ' ', - phutil_tag( - 'a', - array( - 'href' => '/countdown/'.$countdown->getID(), - ), - $countdown->getTitle()), - )); - } + $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); } } diff --git a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php index d002b1e2c3..cf045265b9 100644 --- a/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php +++ b/src/applications/dashboard/customfield/PhabricatorDashboardPanelCoreCustomField.php @@ -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( diff --git a/src/applications/differential/controller/DifferentialController.php b/src/applications/differential/controller/DifferentialController.php index 4f38015ece..46ac290c09 100644 --- a/src/applications/differential/controller/DifferentialController.php +++ b/src/applications/differential/controller/DifferentialController.php @@ -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; + } + + } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 71f4932fed..ccd24220b5 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -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); + } + + } diff --git a/src/applications/differential/customfield/DifferentialUnitField.php b/src/applications/differential/customfield/DifferentialUnitField.php index 17973be7a6..fa2016b106 100644 --- a/src/applications/differential/customfield/DifferentialUnitField.php +++ b/src/applications/differential/customfield/DifferentialUnitField.php @@ -1,7 +1,7 @@ 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; - } } diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index d0cdcf1830..913abad3ab 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -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(); } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index ff72b46de7..e67208c53a 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -432,6 +432,10 @@ final class DifferentialRevision extends DifferentialDAO /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getHarbormasterContainerPHID(); + } + public function getHarbormasterBuildablePHID() { return $this->loadActiveDiff()->getPHID(); } diff --git a/src/applications/drydock/application/PhabricatorDrydockApplication.php b/src/applications/drydock/application/PhabricatorDrydockApplication.php index d19e7cb78d..fef2c843ff 100644 --- a/src/applications/drydock/application/PhabricatorDrydockApplication.php +++ b/src/applications/drydock/application/PhabricatorDrydockApplication.php @@ -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[^/]+)/)?' => 'DrydockAuthorizationListController', ), - 'create/' => 'DrydockBlueprintCreateController', - 'edit/(?:(?P[1-9]\d*)/)?' => 'DrydockBlueprintEditController', + $this->getEditRoutePattern('edit/') + => 'DrydockBlueprintEditController', ), '(?Presource)/' => array( '(?:query/(?P[^/]+)/)?' => 'DrydockResourceListController', diff --git a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php index 443b0e8965..ef904e1019 100644 --- a/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php @@ -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 '. diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index 01c2067280..76a81d7ef1 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -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(); diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index d2f7191c06..8b138405eb 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -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.'); } diff --git a/src/applications/drydock/controller/DrydockBlueprintController.php b/src/applications/drydock/controller/DrydockBlueprintController.php index b995192eb4..c251871426 100644 --- a/src/applications/drydock/controller/DrydockBlueprintController.php +++ b/src/applications/drydock/controller/DrydockBlueprintController.php @@ -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; } diff --git a/src/applications/drydock/controller/DrydockBlueprintCreateController.php b/src/applications/drydock/controller/DrydockBlueprintCreateController.php deleted file mode 100644 index 5398a2327d..0000000000 --- a/src/applications/drydock/controller/DrydockBlueprintCreateController.php +++ /dev/null @@ -1,79 +0,0 @@ -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, - )); - } - -} diff --git a/src/applications/drydock/controller/DrydockBlueprintEditController.php b/src/applications/drydock/controller/DrydockBlueprintEditController.php index 2e61ec868c..f25dc01bff 100644 --- a/src/applications/drydock/controller/DrydockBlueprintEditController.php +++ b/src/applications/drydock/controller/DrydockBlueprintEditController.php @@ -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.'); } } - $policies = id(new PhabricatorPolicyQuery()) - ->setViewer($viewer) - ->setObject($blueprint) - ->execute(); + $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); + } + + $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)); + id(new AphrontFormSubmitControl()) + ->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); } } diff --git a/src/applications/drydock/controller/DrydockBlueprintListController.php b/src/applications/drydock/controller/DrydockBlueprintListController.php index ff09f19ff8..91de6e3425 100644 --- a/src/applications/drydock/controller/DrydockBlueprintListController.php +++ b/src/applications/drydock/controller/DrydockBlueprintListController.php @@ -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; } diff --git a/src/applications/drydock/controller/DrydockBlueprintViewController.php b/src/applications/drydock/controller/DrydockBlueprintViewController.php index b23b869fd4..21f3a395d3 100644 --- a/src/applications/drydock/controller/DrydockBlueprintViewController.php +++ b/src/applications/drydock/controller/DrydockBlueprintViewController.php @@ -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( diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationController.php b/src/applications/drydock/controller/DrydockRepositoryOperationController.php new file mode 100644 index 0000000000..67b580b01a --- /dev/null +++ b/src/applications/drydock/controller/DrydockRepositoryOperationController.php @@ -0,0 +1,11 @@ +newApplicationMenu() + ->setSearchEngine(new DrydockRepositoryOperationSearchEngine()); + } + +} diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php index 28e14bcb90..eb0fb5bb4b 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationDismissController.php @@ -1,7 +1,7 @@ getViewer(); diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationListController.php b/src/applications/drydock/controller/DrydockRepositoryOperationListController.php index 581302817b..42350d2b2a 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationListController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationListController.php @@ -1,37 +1,16 @@ 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(); } } diff --git a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php index 6503886481..c6d38791f3 100644 --- a/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php +++ b/src/applications/drydock/controller/DrydockRepositoryOperationStatusController.php @@ -1,7 +1,7 @@ setUser($viewer) ->setOperation($operation); - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, - $status_view, - ), - array( - 'title' => $title, - )); - + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $object_box, + $status_view, + )); } private function buildActionListView(DrydockRepositoryOperation $operation) { diff --git a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php index 9af418b4d1..32a4ac1ed8 100644 --- a/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php +++ b/src/applications/drydock/customfield/DrydockBlueprintCoreCustomField.php @@ -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( diff --git a/src/applications/drydock/editor/DrydockBlueprintEditEngine.php b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php new file mode 100644 index 0000000000..bec86e5d4c --- /dev/null +++ b/src/applications/drydock/editor/DrydockBlueprintEditEngine.php @@ -0,0 +1,115 @@ +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()), + ); + } + +} diff --git a/src/applications/drydock/editor/DrydockBlueprintEditor.php b/src/applications/drydock/editor/DrydockBlueprintEditor.php index b5fd584945..125c36811a 100644 --- a/src/applications/drydock/editor/DrydockBlueprintEditor.php +++ b/src/applications/drydock/editor/DrydockBlueprintEditor.php @@ -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; + } + } diff --git a/src/applications/drydock/query/DrydockBlueprintQuery.php b/src/applications/drydock/query/DrydockBlueprintQuery.php index 169e47b4f7..6c92927bb8 100644 --- a/src/applications/drydock/query/DrydockBlueprintQuery.php +++ b/src/applications/drydock/query/DrydockBlueprintQuery.php @@ -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(); } diff --git a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php index 64859649df..3c80ca2700 100644 --- a/src/applications/drydock/query/DrydockBlueprintSearchEngine.php +++ b/src/applications/drydock/query/DrydockBlueprintSearchEngine.php @@ -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); } diff --git a/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php b/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php index c4befe8201..1de32be28f 100644 --- a/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php +++ b/src/applications/drydock/query/DrydockRepositoryOperationSearchEngine.php @@ -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); } diff --git a/src/applications/drydock/storage/DrydockBlueprint.php b/src/applications/drydock/storage/DrydockBlueprint.php index 4bc3dbe86d..a7deb73cc3 100644 --- a/src/applications/drydock/storage/DrydockBlueprint.php +++ b/src/applications/drydock/storage/DrydockBlueprint.php @@ -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()), + ); + } + } diff --git a/src/applications/drydock/storage/DrydockBlueprintNameNgrams.php b/src/applications/drydock/storage/DrydockBlueprintNameNgrams.php new file mode 100644 index 0000000000..f140e11ca9 --- /dev/null +++ b/src/applications/drydock/storage/DrydockBlueprintNameNgrams.php @@ -0,0 +1,18 @@ +buildEdgeSchemata(new DrydockBlueprint()); + } + +} diff --git a/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php b/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php index 8b838cd2b6..79229bfab5 100644 --- a/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php +++ b/src/applications/drydock/typeahead/DrydockBlueprintDatasource.php @@ -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; } } diff --git a/src/applications/fund/controller/FundInitiativeViewController.php b/src/applications/fund/controller/FundInitiativeViewController.php index 6f780e4e72..f4535d574e 100644 --- a/src/applications/fund/controller/FundInitiativeViewController.php +++ b/src/applications/fund/controller/FundInitiativeViewController.php @@ -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( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, - 'pageObjects' => array($initiative->getPHID()), + $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( + $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); diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php index 97fa40dee8..d111f266a6 100644 --- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php +++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php @@ -76,16 +76,17 @@ final class PhabricatorHarbormasterApplication extends PhabricatorApplication { => 'HarbormasterBuildActionController', ), 'plan/' => array( - '(?:query/(?P[^/]+)/)?' - => 'HarbormasterPlanListController', - 'edit/(?:(?P\d+)/)?' => 'HarbormasterPlanEditController', + $this->getQueryRoutePattern() => 'HarbormasterPlanListController', + $this->getEditRoutePattern('edit/') + => 'HarbormasterPlanEditController', 'order/(?:(?P\d+)/)?' => 'HarbormasterPlanOrderController', 'disable/(?P\d+)/' => 'HarbormasterPlanDisableController', 'run/(?P\d+)/' => 'HarbormasterPlanRunController', '(?P\d+)/' => 'HarbormasterPlanViewController', ), 'unit/' => array( - '(?P\d+)/' => 'HarbormasterUnitMessagesController', + '(?P\d+)/' => 'HarbormasterUnitMessageListController', + 'view/(?P\d+)/' => 'HarbormasterUnitMessageViewController', ), 'lint/' => array( '(?P\d+)/' => 'HarbormasterLintMessagesController', diff --git a/src/applications/harbormaster/constants/HarbormasterUnitStatus.php b/src/applications/harbormaster/constants/HarbormasterUnitStatus.php new file mode 100644 index 0000000000..de7c4dd42f --- /dev/null +++ b/src/applications/harbormaster/constants/HarbormasterUnitStatus.php @@ -0,0 +1,90 @@ + 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', + ), + ); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php index b7e6dcd978..99595335be 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php @@ -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(); } } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 91fb45176f..22d598431f 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -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; } diff --git a/src/applications/harbormaster/controller/HarbormasterController.php b/src/applications/harbormaster/controller/HarbormasterController.php index dea76b36d3..286b186811 100644 --- a/src/applications/harbormaster/controller/HarbormasterController.php +++ b/src/applications/harbormaster/controller/HarbormasterController.php @@ -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) { diff --git a/src/applications/harbormaster/controller/HarbormasterPlanController.php b/src/applications/harbormaster/controller/HarbormasterPlanController.php index d158c69bed..c70e74099f 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanController.php @@ -2,6 +2,11 @@ abstract class HarbormasterPlanController extends HarbormasterController { + public function buildApplicationMenu() { + return $this->newApplicationMenu() + ->setSearchEngine(new HarbormasterBuildPlanSearchEngine()); + } + protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); diff --git a/src/applications/harbormaster/controller/HarbormasterPlanEditController.php b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php index a4f177e7dc..82af16e4b0 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanEditController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php @@ -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(); } } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanListController.php b/src/applications/harbormaster/controller/HarbormasterPlanListController.php index a67debd533..357c08c460 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanListController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanListController.php @@ -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; } - } diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php index fb664dc084..85bdbd8b49 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -1,6 +1,6 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php index f4212c38b3..847eaa6328 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php @@ -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); diff --git a/src/applications/harbormaster/controller/HarbormasterStepAddController.php b/src/applications/harbormaster/controller/HarbormasterStepAddController.php index a5ec41b2e7..06412e2323 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepAddController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepAddController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php b/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php index 1aa8c24e4a..b4890a671a 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepDeleteController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterStepEditController.php b/src/applications/harbormaster/controller/HarbormasterStepEditController.php index f15a3235b9..49c4563183 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepEditController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepEditController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterStepViewController.php b/src/applications/harbormaster/controller/HarbormasterStepViewController.php index 8f8abf6ea9..faa4b62c7e 100644 --- a/src/applications/harbormaster/controller/HarbormasterStepViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterStepViewController.php @@ -1,6 +1,7 @@ getViewer(); diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php similarity index 84% rename from src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php rename to src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php index ada83f9a5a..2e7f20a71e 100644 --- a/src/applications/harbormaster/controller/HarbormasterUnitMessagesController.php +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageListController.php @@ -1,6 +1,6 @@ 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')); diff --git a/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php new file mode 100644 index 0000000000..652c5a2e23 --- /dev/null +++ b/src/applications/harbormaster/controller/HarbormasterUnitMessageViewController.php @@ -0,0 +1,122 @@ +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; + } +} diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php new file mode 100644 index 0000000000..342156c101 --- /dev/null +++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditEngine.php @@ -0,0 +1,89 @@ +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()), + ); + } + +} diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php index 3b45ff108c..71c9283ade 100644 --- a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php +++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php @@ -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); diff --git a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php index a5517a6b2b..0e852b72a3 100644 --- a/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php +++ b/src/applications/harbormaster/interface/HarbormasterBuildableInterface.php @@ -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(); } diff --git a/src/applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php new file mode 100644 index 0000000000..f6808b1fd5 --- /dev/null +++ b/src/applications/harbormaster/management/HarbormasterManagementArchiveLogsWorkflow.php @@ -0,0 +1,150 @@ +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!')); + } + } + +} diff --git a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php index c6ccabf515..674393dfc7 100644 --- a/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php +++ b/src/applications/harbormaster/phid/HarbormasterBuildablePHIDType.php @@ -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}"); } } diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php index efa04b037b..4058325140 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php @@ -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'; } diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php index 2a05cc21f3..709df7f94c 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php @@ -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); } diff --git a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php index 2986bb45b7..b1a643cac7 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php @@ -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,60 +93,23 @@ final class HarbormasterBuildableQuery } protected function didFilterPage(array $page) { - if ($this->needContainerObjects || $this->needContainerHandles) { + if ($this->needContainerObjects) { $container_phids = array_filter(mpull($page, 'getContainerPHID')); - if ($this->needContainerObjects) { - $containers = array(); - - if ($container_phids) { - $containers = id(new PhabricatorObjectQuery()) - ->setViewer($this->getViewer()) - ->withPHIDs($container_phids) - ->setParentQuery($this) - ->execute(); - $containers = mpull($containers, null, 'getPHID'); - } - - foreach ($page as $key => $buildable) { - $container_phid = $buildable->getContainerPHID(); - $buildable->attachContainerObject(idx($containers, $container_phid)); - } - } - - 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()) + if ($container_phids) { + $containers = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) - ->withPHIDs($handle_phids) + ->withPHIDs($container_phids) ->setParentQuery($this) ->execute(); + $containers = mpull($containers, null, 'getPHID'); + } else { + $containers = array(); } foreach ($page as $key => $buildable) { - $handle_phid = $buildable->getBuildablePHID(); - $buildable->attachBuildableHandle(idx($handles, $handle_phid)); + $container_phid = $buildable->getContainerPHID(); + $buildable->attachContainerObject(idx($containers, $container_phid)); } } @@ -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, diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index 195a68a695..cfff27b1aa 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -11,148 +11,87 @@ final class HarbormasterBuildableSearchEngine return 'PhabricatorHarbormasterApplication'; } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $revisions = $this->readPHIDsFromRequest( - $request, - 'revisions', - array( - DifferentialRevisionPHIDType::TYPECONST, - )); - - $repositories = $this->readPHIDsFromRequest( - $request, - 'repositories', - array( - PhabricatorRepositoryRepositoryPHIDType::TYPECONST, - )); - - $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) - ->execute(); - $diffs = mpull($diffs, 'getPHID', 'getPHID'); - } - - $buildable_phids = array_merge($commits, $diffs); - $saved->setParameter('buildablePHIDs', $buildable_phids); - - $saved->setParameter( - 'manual', - $this->readBoolFromRequest($request, 'manual')); - - return $saved; + public function newQuery() { + return new HarbormasterBuildableQuery(); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { - $query = id(new HarbormasterBuildableQuery()) - ->needContainerHandles(true) - ->needBuildableHandles(true); - - $container_phids = $saved->getParameter('containerPHIDs', array()); - if ($container_phids) { - $query->withContainerPHIDs($container_phids); - } - - $buildable_phids = $saved->getParameter('buildablePHIDs', array()); - - if ($buildable_phids) { - $query->withBuildablePHIDs($buildable_phids); - } - - $manual = $saved->getParameter('manual'); - if ($manual !== null) { - $query->withManualBuildables($manual); - } - - return $query; + 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')), + ); } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved_query) { + private function resolvePHIDs(array $names) { + $viewer = $this->requireViewer(); - $container_phids = $saved_query->getParameter('containerPHIDs', array()); - $buildable_phids = $saved_query->getParameter('buildablePHIDs', array()); + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames($names) + ->execute(); - $all_phids = array_merge($container_phids, $buildable_phids); + // 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('-'); + } - $revision_names = array(); - $diff_names = array(); - $repository_names = array(); - $commit_names = array(); + return mpull($objects, 'getPHID'); + } - if ($all_phids) { - $objects = id(new PhabricatorObjectQuery()) - ->setViewer($this->requireViewer()) - ->withPHIDs($all_phids) - ->execute(); + protected function buildQueryFromParameters(array $map) { + $query = $this->newQuery(); - 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()); - } + if ($map['objectPHIDs']) { + $phids = $this->resolvePHIDs($map['objectPHIDs']); + if ($phids) { + $query->withBuildablePHIDs($phids); } } - $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'), - ))); + if ($map['containerPHIDs']) { + $phids = $this->resolvePHIDs($map['containerPHIDs']); + if ($phids) { + $query->withContainerPHIDs($phids); + } + } + + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + + if ($map['manual'] !== null) { + $query->withManualBuildables($map['manual']); + } + + return $query; } protected function getURI($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(); diff --git a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php index 7f63eda072..eadf4c94f5 100644 --- a/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterWaitForPreviousBuildStepImplementation.php @@ -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); diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 6286072b42..7a7b32618c 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -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 diff --git a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php index 60d942596e..b310db14de 100644 --- a/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php +++ b/src/applications/harbormaster/storage/HarbormasterSchemaSpec.php @@ -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'), - ), - )); - } } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 1bac8d3ecd..4d9278c7bf 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -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 )----------------------------------------------------- */ diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php index 85e7ae2411..40090d0ac3 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php @@ -1,6 +1,7 @@ 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); + $content = (string)$content; + + $this->rope->append($content); + $this->flush(); + } + + private function flush() { + + // 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; } - $this->append($current); - return; - } - // 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) { + $conn_w = $this->establishConnection('w'); + $last = $this->loadLastChunkInfo(); - // 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); - } else { - // We have a resulting record that we can append our content onto. - queryfx( - $conn, - 'UPDATE harbormaster_buildlogchunk '. - 'SET chunk = CONCAT(chunk, %B), size = LENGTH(CONCAT(chunk, %B))'. - 'WHERE id = %d', - $content, - $content, - $result[0]['id']); + $can_append = + ($last) && + ($last['encoding'] == $encoding_text) && + ($last['size'] < $chunk_limit); + if ($can_append) { + $append_id = $last['id']; + $prefix_size = $last['size']; + } else { + $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_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(); } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php new file mode 100644 index 0000000000..e69bad4caf --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunk.php @@ -0,0 +1,61 @@ + 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; + } + + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php new file mode 100644 index 0000000000..754248cc67 --- /dev/null +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLogChunkIterator.php @@ -0,0 +1,49 @@ +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; + } + +} diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php index 27655189e6..8b47bdfc21 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php @@ -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; } diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php index 1062b64a3a..956850bd9f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php @@ -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(), diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php index 5ac9696813..c5b2142ac5 100644 --- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php @@ -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()), + ); + } + } diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php new file mode 100644 index 0000000000..e91daad8ce --- /dev/null +++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanNameNgrams.php @@ -0,0 +1,18 @@ +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); } } diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php new file mode 100644 index 0000000000..75de4b20df --- /dev/null +++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php @@ -0,0 +1,104 @@ +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; + } + +} diff --git a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php index ac3014dc29..fa3a01272b 100644 --- a/src/applications/harbormaster/worker/HarbormasterTargetWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterTargetWorker.php @@ -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); } diff --git a/src/applications/herald/controller/HeraldRuleViewController.php b/src/applications/herald/controller/HeraldRuleViewController.php index 3b209dd960..4036be250b 100644 --- a/src/applications/herald/controller/HeraldRuleViewController.php +++ b/src/applications/herald/controller/HeraldRuleViewController.php @@ -18,7 +18,8 @@ final class HeraldRuleViewController extends HeraldController { $header = id(new PHUIHeaderView()) ->setUser($viewer) ->setHeader($rule->getName()) - ->setPolicyObject($rule); + ->setPolicyObject($rule) + ->setHeaderIcon('fa-bullhorn'); if ($rule->getIsDisabled()) { $header->setStatus( @@ -33,12 +34,15 @@ final class HeraldRuleViewController extends HeraldController { } $actions = $this->buildActionView($rule); - $properties = $this->buildPropertyView($rule, $actions); + $properties = $this->buildPropertyView($rule); + $details = $this->buildPropertySectionView($rule); + $description = $this->buildDescriptionView($rule); $id = $rule->getID(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb("H{$id}"); + $crumbs->setBorder(true); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) @@ -51,13 +55,20 @@ final class HeraldRuleViewController extends HeraldController { $title = $rule->getName(); + $view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setMainColumn($timeline) + ->addPropertySection(pht('DETAILS'), $details) + ->addPropertySection(pht('DESCRIPTION'), $description) + ->setPropertyList($properties) + ->setActionList($actions); + return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild( array( - $object_box, - $timeline, + $view, )); } @@ -105,15 +116,24 @@ final class HeraldRuleViewController extends HeraldController { } private function buildPropertyView( - HeraldRule $rule, - PhabricatorActionListView $actions) { + HeraldRule $rule) { $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($rule) - ->setActionList($actions); + ->setObject($rule); + + $view->invokeWillRenderEvent(); + + return $view; + } + + private function buildPropertySectionView( + HeraldRule $rule) { + + $viewer = $this->getRequest()->getUser(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); $view->addProperty( pht('Rule Type'), @@ -138,19 +158,24 @@ final class HeraldRuleViewController extends HeraldController { pht('Trigger Object'), $viewer->renderHandle($rule->getTriggerObjectPHID())); } - - $view->invokeWillRenderEvent(); - - $view->addSectionHeader( - pht('Rule Description'), - PHUIPropertyListView::ICON_SUMMARY); - - $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); - $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); - $view->addTextContent($rule_text); } return $view; } + private function buildDescriptionView(HeraldRule $rule) { + $viewer = $this->getRequest()->getUser(); + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $adapter = HeraldAdapter::getAdapterForContentType($rule->getContentType()); + if ($adapter) { + $handles = $viewer->loadHandles(HeraldAdapter::getHandlePHIDs($rule)); + $rule_text = $adapter->renderRuleAsText($rule, $handles, $viewer); + $view->addTextContent($rule_text); + return $view; + } + return null; + } + } diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php index fb6db3cf07..4e43c796cf 100644 --- a/src/applications/macro/controller/PhabricatorMacroViewController.php +++ b/src/applications/macro/controller/PhabricatorMacroViewController.php @@ -20,29 +20,18 @@ final class PhabricatorMacroViewController return new Aphront404Response(); } - $file = $macro->getFile(); - $title_short = pht('Macro "%s"', $macro->getName()); $title_long = pht('Image Macro "%s"', $macro->getName()); $actions = $this->buildActionView($macro); + $subheader = $this->buildSubheaderView($macro); + $properties = $this->buildPropertyView($macro); + $file = $this->buildFileView($macro); + $details = $this->buildPropertySectionView($macro); $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb( - $title_short, - $this->getApplicationURI('/view/'.$macro->getID().'/')); - - $properties = $this->buildPropertyView($macro, $actions); - if ($file) { - $file_view = new PHUIPropertyListView(); - $file_view->addImageContent( - phutil_tag( - 'img', - array( - 'src' => $file->getViewURI(), - 'class' => 'phabricator-image-macro-hero', - ))); - } + $crumbs->addTextCrumb($macro->getName()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $macro, @@ -75,28 +64,30 @@ final class PhabricatorMacroViewController ->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/')) ->setSubmitButtonName(pht('Add Comment')); - $object_box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); - - if ($file_view) { - $object_box->addPropertyList($file_view); - } - - return $this->buildApplicationPage( - array( - $crumbs, - $object_box, + ->setSubheader($subheader) + ->setMainColumn(array( $timeline, $add_comment_form, - ), - array( - 'title' => $title_short, - 'pageObjects' => array($macro->getPHID()), + )) + ->addPropertySection(pht('MACRO'), $file) + ->addPropertySection(pht('DETAILS'), $details) + ->setPropertyList($properties) + ->setActionList($actions); + + return $this->newPage() + ->setTitle($title_short) + ->setCrumbs($crumbs) + ->setPageObjectPHIDs(array($macro->getPHID())) + ->appendChild( + array( + $view, )); } - private function buildActionView(PhabricatorFileImageMacro $macro) { + private function buildActionView( + PhabricatorFileImageMacro $macro) { $can_manage = $this->hasApplicationCapability( PhabricatorMacroManageCapability::CAPABILITY); @@ -141,15 +132,34 @@ final class PhabricatorMacroViewController return $view; } - private function buildPropertyView( - PhabricatorFileImageMacro $macro, - PhabricatorActionListView $actions) { + private function buildSubheaderView( + PhabricatorFileImageMacro $macro) { + $viewer = $this->getViewer(); + + $author_phid = $macro->getAuthorPHID(); + + $author = $viewer->renderHandle($author_phid)->render(); + $date = phabricator_datetime($macro->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $handles = $viewer->loadHandles(array($author_phid)); + $image_uri = $handles[$author_phid]->getImageURI(); + $image_href = $handles[$author_phid]->getURI(); + + $content = pht('Masterfully imagined by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + + private function buildPropertySectionView( + PhabricatorFileImageMacro $macro) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) - ->setUser($this->getRequest()->getUser()) - ->setObject($macro) - ->setActionList($actions); + ->setUser($viewer); switch ($macro->getAudioBehavior()) { case PhabricatorFileImageMacro::AUDIO_BEHAVIOR_ONCE: @@ -167,6 +177,38 @@ final class PhabricatorMacroViewController $viewer->renderHandle($audio_phid)); } + return $view; + } + + private function buildFileView( + PhabricatorFileImageMacro $macro) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $file = $macro->getFile(); + if ($file) { + $view->addImageContent( + phutil_tag( + 'img', + array( + 'src' => $file->getViewURI(), + 'class' => 'phabricator-image-macro-hero', + ))); + return $view; + } + return null; + } + + private function buildPropertyView( + PhabricatorFileImageMacro $macro) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($this->getRequest()->getUser()) + ->setObject($macro); + $view->invokeWillRenderEvent(); return $view; diff --git a/src/applications/passphrase/controller/PassphraseCredentialViewController.php b/src/applications/passphrase/controller/PassphraseCredentialViewController.php index c156be81ce..5ec2fe7c07 100644 --- a/src/applications/passphrase/controller/PassphraseCredentialViewController.php +++ b/src/applications/passphrase/controller/PassphraseCredentialViewController.php @@ -28,23 +28,28 @@ final class PassphraseCredentialViewController extends PassphraseController { $title = pht('%s %s', 'K'.$credential->getID(), $credential->getName()); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('K'.$credential->getID()); + $crumbs->setBorder(true); $header = $this->buildHeaderView($credential); $actions = $this->buildActionView($credential, $type); - $properties = $this->buildPropertyView($credential, $type, $actions); + $properties = $this->buildPropertyView($credential, $type); + $subheader = $this->buildSubheaderView($credential); + $content = $this->buildPropertySectionView($credential, $type); - $box = id(new PHUIObjectBoxView()) + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); + ->setSubheader($subheader) + ->setMainColumn($timeline) + ->addPropertySection(pht('PROPERTIES'), $content) + ->setPropertyList($properties) + ->setActionList($actions); - return $this->buildApplicationPage( - array( - $crumbs, - $box, - $timeline, - ), - array( - 'title' => $title, + return $this->newPage() + ->setTitle($title) + ->setCrumbs($crumbs) + ->appendChild( + array( + $view, )); } @@ -63,6 +68,35 @@ final class PassphraseCredentialViewController extends PassphraseController { return $header; } + private function buildSubheaderView( + PassphraseCredential $credential) { + $viewer = $this->getViewer(); + + $author = $viewer->renderHandle($credential->getAuthorPHID())->render(); + $date = phabricator_datetime($credential->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $person = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($credential->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + if (!$person) { + return null; + } + + $image_uri = $person->getProfileImageURI(); + $image_href = '/p/'.$credential->getUsername(); + + $content = pht('Created by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildActionView( PassphraseCredential $credential, PassphraseCredentialType $type) { @@ -153,16 +187,13 @@ final class PassphraseCredentialViewController extends PassphraseController { return $actions; } - private function buildPropertyView( + private function buildPropertySectionView( PassphraseCredential $credential, - PassphraseCredentialType $type, - PhabricatorActionListView $actions) { + PassphraseCredentialType $type) { $viewer = $this->getRequest()->getUser(); $properties = id(new PHUIPropertyListView()) - ->setUser($viewer) - ->setObject($credential) - ->setActionList($actions); + ->setUser($viewer); $properties->addProperty( pht('Credential Type'), @@ -192,8 +223,6 @@ final class PassphraseCredentialViewController extends PassphraseController { $viewer->renderHandleList($used_by_phids)); } - $properties->invokeWillRenderEvent(); - $description = $credential->getDescription(); if (strlen($description)) { $properties->addSectionHeader( @@ -206,4 +235,17 @@ final class PassphraseCredentialViewController extends PassphraseController { return $properties; } + private function buildPropertyView( + PassphraseCredential $credential, + PassphraseCredentialType $type) { + $viewer = $this->getRequest()->getUser(); + + $properties = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setObject($credential); + + $properties->invokeWillRenderEvent(); + return $properties; + } + } diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php index 530b2245c5..f259cdc6af 100644 --- a/src/applications/paste/controller/PhabricatorPasteViewController.php +++ b/src/applications/paste/controller/PhabricatorPasteViewController.php @@ -48,25 +48,16 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $header = $this->buildHeaderView($paste); $actions = $this->buildActionView($viewer, $paste); - $properties = $this->buildPropertyView($paste, $fork_phids, $actions); - - $object_box = id(new PHUIObjectBoxView()) - ->setHeader($header) - ->addPropertyList($properties); - + $properties = $this->buildPropertyView($paste, $fork_phids); + $subheader = $this->buildSubheaderView($paste); $source_code = $this->buildSourceCodeView($paste, $this->highlightMap); require_celerity_resource('paste-css'); - $source_code = phutil_tag( - 'div', - array( - 'class' => 'container-of-paste', - ), - $source_code); $monogram = $paste->getMonogram(); $crumbs = $this->buildApplicationCrumbs() - ->addTextCrumb($monogram, '/'.$monogram); + ->addTextCrumb($monogram) + ->setBorder(true); $timeline = $this->buildTransactionTimeline( $paste, @@ -79,6 +70,18 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { $timeline->setQuoteRef($monogram); $comment_view->setTransactionTimeline($timeline); + $paste_view = id(new PHUITwoColumnView()) + ->setHeader($header) + ->setSubheader($subheader) + ->setMainColumn(array( + $source_code, + $timeline, + $comment_view, + )) + ->setPropertyList($properties) + ->setActionList($actions) + ->addClass('ponder-question-view'); + return $this->newPage() ->setTitle($paste->getFullName()) ->setCrumbs($crumbs) @@ -86,13 +89,7 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { array( $paste->getPHID(), )) - ->appendChild( - array( - $object_box, - $source_code, - $timeline, - $comment_view, - )); + ->appendChild($paste_view); } private function buildHeaderView(PhabricatorPaste $paste) { @@ -167,24 +164,40 @@ final class PhabricatorPasteViewController extends PhabricatorPasteController { return $action_list; } + + private function buildSubheaderView( + PhabricatorPaste $paste) { + $viewer = $this->getViewer(); + + $author = $viewer->renderHandle($paste->getAuthorPHID())->render(); + $date = phabricator_datetime($paste->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $author_info = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($paste->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + $image_uri = $author_info->getProfileImageURI(); + $image_href = '/p/'.$author_info->getUsername(); + + $content = pht('Authored by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildPropertyView( PhabricatorPaste $paste, - array $child_phids, - PhabricatorActionListView $actions) { + array $child_phids) { $viewer = $this->getViewer(); $properties = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($paste) - ->setActionList($actions); - - $properties->addProperty( - pht('Author'), - $viewer->renderHandle($paste->getAuthorPHID())); - - $properties->addProperty( - pht('Created'), - phabricator_datetime($paste->getDateCreated(), $viewer)); + ->setObject($paste); if ($paste->getParentPHID()) { $properties->addProperty( diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php index 698ff983f1..60b02b01f4 100644 --- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php +++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php @@ -52,7 +52,10 @@ final class PhabricatorPeopleProfileViewController $name = $user->getUsername(); $feed = $this->buildPeopleFeed($user, $viewer); - $feed = phutil_tag_div('project-view-feed', $feed); + $feed = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Activity')) + ->addClass('project-view-feed') + ->appendChild($feed); $projects = $this->buildProjectsView($user); $badges = $this->buildBadgesView($user); @@ -106,8 +109,7 @@ final class PhabricatorPeopleProfileViewController return null; } - $view = id(new PHUIBoxView()) - ->setBorder(true) + $view = id(new PHUIObjectBoxView()) ->appendChild($view) ->addClass('project-view-properties'); diff --git a/src/applications/phame/application/PhabricatorPhameApplication.php b/src/applications/phame/application/PhabricatorPhameApplication.php index 637b51a66c..30d1b9cb3d 100644 --- a/src/applications/phame/application/PhabricatorPhameApplication.php +++ b/src/applications/phame/application/PhabricatorPhameApplication.php @@ -60,11 +60,11 @@ final class PhabricatorPhameApplication extends PhabricatorApplication { 'blog/' => array( '(?:query/(?P[^/]+)/)?' => 'PhameBlogListController', 'archive/(?P[^/]+)/' => 'PhameBlogArchiveController', - 'edit/(?P[^/]+)/' => 'PhameBlogEditController', + $this->getEditRoutePattern('edit/') + => 'PhameBlogEditController', 'view/(?P\d+)/' => 'PhameBlogViewController', 'manage/(?P[^/]+)/' => 'PhameBlogManageController', 'feed/(?P[^/]+)/' => 'PhameBlogFeedController', - 'new/' => 'PhameBlogEditController', 'picture/(?P[1-9]\d*)/' => 'PhameBlogProfilePictureController', ), ) + $this->getResourceSubroutes(), diff --git a/src/applications/phame/controller/PhameHomeController.php b/src/applications/phame/controller/PhameHomeController.php index cea86735b5..95d1544abe 100644 --- a/src/applications/phame/controller/PhameHomeController.php +++ b/src/applications/phame/controller/PhameHomeController.php @@ -6,6 +6,16 @@ final class PhameHomeController extends PhamePostController { return true; } + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + id(new PhameBlogEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); + + return $crumbs; + } + public function handleRequest(AphrontRequest $request) { $viewer = $request->getViewer(); @@ -44,7 +54,7 @@ final class PhameHomeController extends PhamePostController { $create_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Create a Blog')) - ->setHref('/phame/blog/new/') + ->setHref('/phame/blog/edit/') ->setColor(PHUIButtonView::GREEN); $post_list = id(new PHUIBigInfoView()) @@ -116,27 +126,6 @@ final class PhameHomeController extends PhamePostController { array( $phame_home, )); - - - } - - private function renderBlogs($viewer, $blogs) {} - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $can_create = $this->hasApplicationCapability( - PhameBlogCreateCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Blog')) - ->setHref($this->getApplicationURI('/blog/new/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); - - return $crumbs; } } diff --git a/src/applications/phame/controller/blog/PhameBlogListController.php b/src/applications/phame/controller/blog/PhameBlogListController.php index 9bef5c1cf9..ff14178087 100644 --- a/src/applications/phame/controller/blog/PhameBlogListController.php +++ b/src/applications/phame/controller/blog/PhameBlogListController.php @@ -7,43 +7,18 @@ final class PhameBlogListController extends PhameBlogController { } public function handleRequest(AphrontRequest $request) { - $query_key = $request->getURIData('queryKey'); - $controller = id(new PhabricatorApplicationSearchController()) - ->setQueryKey($query_key) - ->setSearchEngine(new PhameBlogSearchEngine()) - ->setNavigation($this->buildSideNavView()); - - return $this->delegateToController($controller); + return id(new PhameBlogSearchEngine()) + ->setController($this) + ->buildResponse(); } - public function buildSideNavView() { - $viewer = $this->getRequest()->getUser(); - - $nav = new AphrontSideNavFilterView(); - $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - - id(new PhameBlogSearchEngine()) - ->setViewer($viewer) - ->addNavigationItems($nav->getMenu()); - - $nav->selectFilter(null); - - return $nav; - } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); - $can_create = $this->hasApplicationCapability( - PhameBlogCreateCapability::CAPABILITY); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('New Blog')) - ->setHref($this->getApplicationURI('/blog/new/')) - ->setIcon('fa-plus-square') - ->setDisabled(!$can_create) - ->setWorkflow(!$can_create)); + id(new PhameBlogEditEngine()) + ->setViewer($this->getViewer()) + ->addActionToCrumbs($crumbs); return $crumbs; } diff --git a/src/applications/phame/editor/PhameBlogEditEngine.php b/src/applications/phame/editor/PhameBlogEditEngine.php index 92e267096d..503a6bbd27 100644 --- a/src/applications/phame/editor/PhameBlogEditEngine.php +++ b/src/applications/phame/editor/PhameBlogEditEngine.php @@ -46,12 +46,24 @@ final class PhameBlogEditEngine return pht('Create Blog'); } + protected function getObjectCreateCancelURI($object) { + return $this->getApplication()->getApplicationURI('blog/'); + } + + protected function getEditorURI() { + return $this->getApplication()->getApplicationURI('blog/edit/'); + } + protected function getObjectViewURI($object) { return $object->getManageURI(); } - protected function buildCustomEditFields($object) { + protected function getCreateNewObjectPolicy() { + return $this->getApplication()->getPolicy( + PhameBlogCreateCapability::CAPABILITY); + } + protected function buildCustomEditFields($object) { return array( id(new PhabricatorTextEditField()) ->setKey('name') diff --git a/src/applications/phame/view/PhameBlogListView.php b/src/applications/phame/view/PhameBlogListView.php index f2149b8cfb..0d897b730a 100644 --- a/src/applications/phame/view/PhameBlogListView.php +++ b/src/applications/phame/view/PhameBlogListView.php @@ -72,7 +72,7 @@ final class PhameBlogListView extends AphrontTagView { $list = phutil_tag( 'a', array( - 'href' => '/phame/blog/new/', + 'href' => '/phame/blog/edit/', ), pht('Create a Blog')); } diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php index 0bbfc4d249..b789fa1bf1 100644 --- a/src/applications/phid/view/PHUIHandleTagListView.php +++ b/src/applications/phid/view/PHUIHandleTagListView.php @@ -53,7 +53,7 @@ final class PHUIHandleTagListView extends AphrontTagView { $handles = $this->handles; // If the list is empty, we may render a "No Projects" tag. - if (!$handles) { + if (!count($handles)) { if (strlen($this->noDataString)) { $no_data_tag = $this->newPlaceholderTag() ->setName($this->noDataString); @@ -61,7 +61,10 @@ final class PHUIHandleTagListView extends AphrontTagView { } } - if ($this->limit) { + if ($this->limit && ($this->limit > count($handles))) { + if (!is_array($handles)) { + $handles = iterator_to_array($handles); + } $handles = array_slice($handles, 0, $this->limit); } diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php index 1b5b21e464..41819a34f0 100644 --- a/src/applications/ponder/controller/PonderQuestionViewController.php +++ b/src/applications/ponder/controller/PonderQuestionViewController.php @@ -31,7 +31,7 @@ final class PonderQuestionViewController extends PonderController { $header->setHeader($question->getTitle()); $header->setUser($viewer); $header->setPolicyObject($question); - $header->setProfileHeader(true); + $header->setHeaderIcon('fa-university'); if ($question->getStatus() == PonderQuestionStatus::STATUS_OPEN) { $header->setStatus('fa-square-o', 'bluegrey', pht('Open')); @@ -43,34 +43,15 @@ final class PonderQuestionViewController extends PonderController { $header->setStatus($icon, 'dark', $text); } - $actions = $this->buildActionListView($question); $properties = $this->buildPropertyListView($question); - $details = $this->buildDetailsPropertyView($question); + $actions = $this->buildActionListView($question); + $details = $this->buildPropertySectionView($question); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $question, PhabricatorPolicyCapability::CAN_EDIT); - $edit_uri = '/question/edit/'.$question->getID().'/'; - $edit_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Edit')) - ->setHref($this->getApplicationURI($edit_uri)) - ->setIcon('fa-pencil') - ->setDisabled(!$can_edit) - ->setWorkflow(!$can_edit); - - $action_button = id(new PHUIButtonView()) - ->setTag('a') - ->setText(pht('Actions')) - ->setHref('#') - ->setIcon('fa-bars') - ->setDropdownMenu($actions); - - $header->addActionLink($action_button); - $header->addActionLink($edit_button); - $content_id = celerity_generate_unique_node_id(); $timeline = $this->buildTransactionTimeline( $question, @@ -81,7 +62,6 @@ final class PonderQuestionViewController extends PonderController { $add_comment = id(new PhabricatorApplicationTransactionCommentView()) ->setUser($viewer) ->setObjectPHID($question->getPHID()) - ->setFullWidth(true) ->setShowPreview(false) ->setAction($this->getApplicationURI("/question/comment/{$id}/")) ->setSubmitButtonName(pht('Comment')); @@ -108,19 +88,14 @@ final class PonderQuestionViewController extends PonderController { $crumbs->addTextCrumb('Q'.$id, '/Q'.$id); $crumbs->setBorder(true); + $subheader = $this->buildSubheaderView($question); + $answer_wiki = null; if ($question->getAnswerWiki()) { $wiki = new PHUIRemarkupView($viewer, $question->getAnswerWiki()); - $wiki_header = phutil_tag( - 'div', - array( - 'class' => 'ponder-answer-wiki-header', - ), - pht('Answer Summary')); - $answer_wiki = id(new PHUIObjectBoxView()) - ->setBackground(PHUIObjectBoxView::BLUE) - ->appendChild($wiki_header) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->setHeaderText(pht('ANSWER SUMMARY')) ->appendChild($wiki) ->addClass('ponder-answer-wiki'); } @@ -133,18 +108,20 @@ final class PonderQuestionViewController extends PonderController { 'class' => 'ponder-question-content', ), array( - $details, + $answer_wiki, $footer, $comment_view, - $answer_wiki, $answers, $answer_add_panel, )); $ponder_view = id(new PHUITwoColumnView()) ->setHeader($header) + ->setSubheader($subheader) ->setMainColumn($ponder_content) - ->setSideColumn($properties) + ->setPropertyList($properties) + ->addPropertySection(pht('DETAILS'), $details) + ->setActionList($actions) ->addClass('ponder-question-view'); $page_objects = array_merge( @@ -183,6 +160,14 @@ final class PonderQuestionViewController extends PonderController { $icon = 'fa-square-o'; } + $view->addAction( + id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Question')) + ->setHref($this->getApplicationURI("/question/edit/{$id}/")) + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit)); + $view->addAction( id(new PhabricatorActionView()) ->setName($name) @@ -206,24 +191,39 @@ final class PonderQuestionViewController extends PonderController { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($question) - ->setStacked(true); + ->setObject($question); $view->invokeWillRenderEvent(); - if (!$view->hasAnyProperties()) { - return null; - } - - $view = id(new PHUIObjectBoxView()) - ->appendChild($view) - ->setBackground(PHUIObjectBoxView::GREY) - ->addClass('ponder-view-properties'); - return $view; } - private function buildDetailsPropertyView( + private function buildSubheaderView( + PonderQuestion $question) { + $viewer = $this->getViewer(); + + $asker = $viewer->renderHandle($question->getAuthorPHID())->render(); + $date = phabricator_datetime($question->getDateCreated(), $viewer); + $asker = phutil_tag('strong', array(), $asker); + + $author = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($question->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + $image_uri = $author->getProfileImageURI(); + $image_href = '/p/'.$author->getUsername(); + + $content = pht('Asked by %s on %s.', $asker, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + + private function buildPropertySectionView( PonderQuestion $question) { $viewer = $this->getViewer(); @@ -239,49 +239,10 @@ final class PonderQuestionViewController extends PonderController { pht('No further details for this question.')); } - $asker = $viewer->renderHandle($question->getAuthorPHID())->render(); - $date = phabricator_datetime($question->getDateCreated(), $viewer); - $asker = phutil_tag('strong', array(), $asker); + $question_details = phutil_tag_div( + 'phabricator-remarkup ml', $question_details); - $author = id(new PhabricatorPeopleQuery()) - ->setViewer($viewer) - ->withPHIDs(array($question->getAuthorPHID())) - ->needProfileImage(true) - ->executeOne(); - - $image_uri = $author->getProfileImageURI(); - $image_href = '/p/'.$author->getUsername(); - - $image = phutil_tag( - 'a', - array( - 'class' => 'ponder-details-author-image', - 'style' => 'background-image: url('.$image_uri.');', - 'href' => $image_href, - )); - - $details_header = phutil_tag( - 'div', - array( - 'class' => 'ponder-details-subtitle', - ), - array( - $image, - pht('Asked by %s on %s.', $asker, $date), - )); - - $details = phutil_tag( - 'div', - array( - 'class' => 'ponder-detail-view', - ), - array( - $details_header, - phutil_tag_div('phabricator-remarkup', $question_details), - )); - - - return $details; + return $question_details; } /** @@ -310,7 +271,6 @@ final class PonderQuestionViewController extends PonderController { ->withTransactionTypes(array(PhabricatorTransactions::TYPE_COMMENT))); $xactions = $timeline->getTransactions(); - $view[] = id(new PonderAnswerView()) ->setUser($viewer) ->setAnswer($answer) diff --git a/src/applications/ponder/view/PonderAddAnswerView.php b/src/applications/ponder/view/PonderAddAnswerView.php index ad17f68af6..989837aa94 100644 --- a/src/applications/ponder/view/PonderAddAnswerView.php +++ b/src/applications/ponder/view/PonderAddAnswerView.php @@ -22,13 +22,14 @@ final class PonderAddAnswerView extends AphrontView { $authors = mpull($question->getAnswers(), null, 'getAuthorPHID'); if (isset($authors[$viewer->getPHID()])) { - return id(new PHUIInfoView()) + $view = id(new PHUIInfoView()) ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) ->setTitle(pht('Already Answered')) ->appendChild( pht( 'You have already answered this question. You can not answer '. 'twice, but you can edit your existing answer.')); + return phutil_tag_div('ponder-add-answer-view', $view); } $info_panel = null; @@ -51,7 +52,6 @@ final class PonderAddAnswerView extends AphrontView { ->setUser($this->user) ->setAction($this->actionURI) ->setWorkflow(true) - ->setFullWidth(true) ->addHiddenInput('question_id', $question->getID()) ->appendChild( id(new PhabricatorRemarkupControl()) @@ -78,7 +78,7 @@ final class PonderAddAnswerView extends AphrontView { $box = id(new PHUIObjectBoxView()) ->appendChild($form) - ->setBackground(PHUIObjectBoxView::GREY) + ->setHeaderText('Answer') ->addClass('ponder-add-answer-view'); if ($info_panel) { diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php index ddbd0ffb5b..f61ebd28d2 100644 --- a/src/applications/project/controller/PhabricatorProjectProfileController.php +++ b/src/applications/project/controller/PhabricatorProjectProfileController.php @@ -82,7 +82,11 @@ final class PhabricatorProjectProfileController ->execute(); $feed = $this->renderStories($stories); - $feed = phutil_tag_div('project-view-feed', $feed); + $feed = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Recent Activity')) + ->addClass('project-view-feed') + ->appendChild($feed); + require_celerity_resource('project-view-css'); $home = id(new PHUITwoColumnView()) @@ -134,8 +138,7 @@ final class PhabricatorProjectProfileController return null; } - $view = id(new PHUIBoxView()) - ->setBorder(true) + $view = id(new PHUIObjectBoxView()) ->appendChild($view) ->addClass('project-view-properties'); diff --git a/src/applications/project/query/PhabricatorProjectQuery.php b/src/applications/project/query/PhabricatorProjectQuery.php index 9845628b4b..ab41ecf0ac 100644 --- a/src/applications/project/query/PhabricatorProjectQuery.php +++ b/src/applications/project/query/PhabricatorProjectQuery.php @@ -187,6 +187,7 @@ final class PhabricatorProjectQuery protected function getPagingValueMap($cursor, array $keys) { $project = $this->loadCursorObject($cursor); return array( + 'id' => $project->getID(), 'name' => $project->getName(), ); } diff --git a/src/applications/project/storage/PhabricatorProjectTransaction.php b/src/applications/project/storage/PhabricatorProjectTransaction.php index ef5d39b4e8..2123afae2c 100644 --- a/src/applications/project/storage/PhabricatorProjectTransaction.php +++ b/src/applications/project/storage/PhabricatorProjectTransaction.php @@ -69,6 +69,21 @@ final class PhabricatorProjectTransaction return parent::getColor(); } + public function shouldHide() { + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_EDGE: + $edge_type = $this->getMetadataValue('edge:type'); + switch ($edge_type) { + case PhabricatorProjectSilencedEdgeType::EDGECONST: + return true; + default: + break; + } + } + + return parent::shouldHide(); + } + public function shouldHideForFeed() { switch ($this->getTransactionType()) { case self::TYPE_HASWORKBOARD: diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 348b94d021..9fd218db9a 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -370,6 +370,10 @@ final class PhabricatorRepositoryCommit /* -( HarbormasterBuildableInterface )------------------------------------- */ + public function getHarbormasterBuildableDisplayPHID() { + return $this->getHarbormasterBuildablePHID(); + } + public function getHarbormasterBuildablePHID() { return $this->getPHID(); } diff --git a/src/applications/search/field/PhabricatorSearchStringListField.php b/src/applications/search/field/PhabricatorSearchStringListField.php index 415caf7ea2..2dd9517a3e 100644 --- a/src/applications/search/field/PhabricatorSearchStringListField.php +++ b/src/applications/search/field/PhabricatorSearchStringListField.php @@ -3,6 +3,17 @@ final class PhabricatorSearchStringListField extends PhabricatorSearchField { + private $placeholder; + + public function setPlaceholder($placeholder) { + $this->placeholder = $placeholder; + return $this; + } + + public function getPlaceholder() { + return $this->placeholder; + } + protected function getDefaultValue() { return array(); } @@ -12,7 +23,14 @@ final class PhabricatorSearchStringListField } protected function newControl() { - return new AphrontFormTextControl(); + $control = new AphrontFormTextControl(); + + $placeholder = $this->getPlaceholder(); + if ($placeholder !== null) { + $control->setPlaceholder($placeholder); + } + + return $control; } protected function getValueForControl() { diff --git a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php index fd05afb057..2eb2c08c75 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvotePollController.php @@ -23,7 +23,6 @@ final class PhabricatorSlowvotePollController } $poll_view = id(new SlowvoteEmbedView()) - ->setHeadless(true) ->setUser($viewer) ->setPoll($poll); @@ -44,22 +43,34 @@ final class PhabricatorSlowvotePollController ->setHeader($poll->getQuestion()) ->setUser($viewer) ->setStatus($header_icon, $header_color, $header_name) - ->setPolicyObject($poll); + ->setPolicyObject($poll) + ->setHeaderIcon('fa-bar-chart'); $actions = $this->buildActionView($poll); - $properties = $this->buildPropertyView($poll, $actions); + $properties = $this->buildPropertyView($poll); + $subheader = $this->buildSubheaderView($poll); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb('V'.$poll->getID()); + $crumbs->setBorder(true); $timeline = $this->buildTransactionTimeline( $poll, new PhabricatorSlowvoteTransactionQuery()); $add_comment = $this->buildCommentForm($poll); - $object_box = id(new PHUIObjectBoxView()) + $poll_content = array( + $poll_view, + $timeline, + $add_comment, + ); + + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->addPropertyList($properties); + ->setSubheader($subheader) + ->setMainColumn($poll_content) + ->setPropertyList($properties) + ->setActionList($actions); return $this->newPage() ->setTitle('V'.$poll->getID().' '.$poll->getQuestion()) @@ -67,10 +78,7 @@ final class PhabricatorSlowvotePollController ->setPageObjectPHIDs(array($poll->getPHID())) ->appendChild( array( - $object_box, - $poll_view, - $timeline, - $add_comment, + $view, )); } @@ -110,30 +118,42 @@ final class PhabricatorSlowvotePollController } private function buildPropertyView( - PhabricatorSlowvotePoll $poll, - PhabricatorActionListView $actions) { + PhabricatorSlowvotePoll $poll) { $viewer = $this->getRequest()->getUser(); - $view = id(new PHUIPropertyListView()) ->setUser($viewer) - ->setObject($poll) - ->setActionList($actions); - + ->setObject($poll); $view->invokeWillRenderEvent(); - $description = $poll->getDescription(); - if (strlen($description)) { - $description = new PHUIRemarkupView($viewer, $description); - $view->addSectionHeader( - pht('Description'), - PHUIPropertyListView::ICON_SUMMARY); - $view->addTextContent($description); - } - return $view; } + private function buildSubheaderView( + PhabricatorSlowvotePoll $poll) { + $viewer = $this->getViewer(); + + $author = $viewer->renderHandle($poll->getAuthorPHID())->render(); + $date = phabricator_datetime($poll->getDateCreated(), $viewer); + $author = phutil_tag('strong', array(), $author); + + $person = id(new PhabricatorPeopleQuery()) + ->setViewer($viewer) + ->withPHIDs(array($poll->getAuthorPHID())) + ->needProfileImage(true) + ->executeOne(); + + $image_uri = $person->getProfileImageURI(); + $image_href = '/p/'.$person->getUsername(); + + $content = pht('Asked by %s on %s.', $author, $date); + + return id(new PHUIHeadThingView()) + ->setImage($image_uri) + ->setImageHref($image_href) + ->setContent($content); + } + private function buildCommentForm(PhabricatorSlowvotePoll $poll) { $viewer = $this->getRequest()->getUser(); diff --git a/src/applications/slowvote/view/SlowvoteEmbedView.php b/src/applications/slowvote/view/SlowvoteEmbedView.php index 3582ea45b3..452cfbe9eb 100644 --- a/src/applications/slowvote/view/SlowvoteEmbedView.php +++ b/src/applications/slowvote/view/SlowvoteEmbedView.php @@ -4,12 +4,6 @@ final class SlowvoteEmbedView extends AphrontView { private $poll; private $handles; - private $headless; - - public function setHeadless($headless) { - $this->headless = $headless; - return $this; - } public function setPoll(PhabricatorSlowvotePoll $poll) { $this->poll = $poll; @@ -68,29 +62,25 @@ final class SlowvoteEmbedView extends AphrontView { ), $poll->getQuestion()); - if ($this->headless) { - $header = null; - } else { - $header = id(new PHUIHeaderView()) - ->setHeader($link_to_slowvote); + $header = id(new PHUIHeaderView()) + ->setHeader($link_to_slowvote); - $description = $poll->getDescription(); - if (strlen($description)) { - $description = new PHUIRemarkupView($this->getUser(), $description); - $description = phutil_tag( - 'div', - array( - 'class' => 'slowvote-description', - ), - $description); - } - - $header = array( - $header, - $description, - ); + $description = $poll->getDescription(); + if (strlen($description)) { + $description = new PHUIRemarkupView($this->getUser(), $description); + $description = phutil_tag( + 'div', + array( + 'class' => 'slowvote-description', + ), + $description); } + $header = array( + $header, + $description, + ); + $vis = $poll->getResponseVisibility(); if ($this->areResultsVisible()) { if ($vis == PhabricatorSlowvotePoll::RESPONSES_OWNER) { @@ -163,8 +153,10 @@ final class SlowvoteEmbedView extends AphrontView { array($body)); return id(new PHUIObjectBoxView()) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) ->setHeader($header) - ->appendChild($embed); + ->appendChild($embed) + ->addClass('slowvote-poll-view'); } private function renderLabel(PhabricatorSlowvoteOption $option, $selected) { diff --git a/src/applications/transactions/editfield/PhabricatorStaticEditField.php b/src/applications/transactions/editfield/PhabricatorStaticEditField.php new file mode 100644 index 0000000000..62c5a9a888 --- /dev/null +++ b/src/applications/transactions/editfield/PhabricatorStaticEditField.php @@ -0,0 +1,18 @@ +getDatasource(); } + protected function getHTTPParameterType() { + return new AphrontPHIDListHTTPParameterType(); + } + + protected function newConduitSearchParameterType() { + return new ConduitPHIDListParameterType(); + } + + protected function newConduitEditParameterType() { + return new ConduitPHIDListParameterType(); + } + } diff --git a/src/view/control/AphrontTableView.php b/src/view/control/AphrontTableView.php index b74ccd3124..176d246718 100644 --- a/src/view/control/AphrontTableView.php +++ b/src/view/control/AphrontTableView.php @@ -15,6 +15,8 @@ final class AphrontTableView extends AphrontView { protected $columnVisibility = array(); private $deviceVisibility = array(); + private $columnWidths = array(); + protected $sortURI; protected $sortParam; protected $sortSelected; @@ -46,6 +48,11 @@ final class AphrontTableView extends AphrontView { return $this; } + public function setColumnWidths(array $widths) { + $this->columnWidths = $widths; + return $this; + } + public function setNoDataString($no_data_string) { $this->noDataString = $no_data_string; return $this; @@ -131,6 +138,8 @@ final class AphrontTableView extends AphrontView { $visibility = array_values($this->columnVisibility); $device_visibility = array_values($this->deviceVisibility); + $column_widths = $this->columnWidths; + $headers = $this->headers; $short_headers = $this->shortHeaders; $sort_values = $this->sortValues; @@ -148,21 +157,6 @@ final class AphrontTableView extends AphrontView { $sort_values[] = null; } - if ($this->notice) { - $colspan = max(count(array_filter($visibility)), 1); - $table[] = phutil_tag( - 'tr', - array(), - phutil_tag( - 'td', - array( - 'colspan' => $colspan, - 'class' => 'aphront-table-notice', - ), - $this->notice)); - - } - $tr = array(); foreach ($headers as $col_num => $header) { if (!$visibility[$col_num]) { @@ -236,7 +230,18 @@ final class AphrontTableView extends AphrontView { $header = hsprintf('%s %s', $header_nodevice, $header_device); } - $tr[] = phutil_tag('th', array('class' => $class), $header); + $style = null; + if (isset($column_widths[$col_num])) { + $style = 'width: '.$column_widths[$col_num].';'; + } + + $tr[] = phutil_tag( + 'th', + array( + 'class' => $class, + 'style' => $style, + ), + $header); } $table[] = phutil_tag('tr', array(), $tr); } @@ -283,7 +288,13 @@ final class AphrontTableView extends AphrontView { if (!empty($this->cellClasses[$row_num][$col_num])) { $class = trim($class.' '.$this->cellClasses[$row_num][$col_num]); } - $tr[] = phutil_tag('td', array('class' => $class), $value); + + $tr[] = phutil_tag( + 'td', + array( + 'class' => $class, + ), + $value); ++$col_num; } @@ -315,17 +326,38 @@ final class AphrontTableView extends AphrontView { if ($this->className !== null) { $classes[] = $this->className; } + if ($this->deviceReadyTable) { $classes[] = 'aphront-table-view-device-ready'; } + if ($this->columnWidths) { + $classes[] = 'aphront-table-view-fixed'; + } + + $notice = null; + if ($this->notice) { + $notice = phutil_tag( + 'div', + array( + 'class' => 'aphront-table-notice', + ), + $this->notice); + } + $html = phutil_tag( 'table', array( 'class' => implode(' ', $classes), ), $table); - return phutil_tag_div('aphront-table-wrap', $html); + + return phutil_tag_div( + 'aphront-table-wrap', + array( + $notice, + $html, + )); } public static function renderSingleDisplayLine($line) { diff --git a/src/view/phui/PHUIHeadThingView.php b/src/view/phui/PHUIHeadThingView.php new file mode 100644 index 0000000000..4ff0290578 --- /dev/null +++ b/src/view/phui/PHUIHeadThingView.php @@ -0,0 +1,65 @@ +imageHref = $href; + return $this; + } + + public function setImage($image) { + $this->image = $image; + return $this; + } + + public function setContent($content) { + $this->content = $content; + return $this; + } + + public function setSize($size) { + $this->size = $size; + return $this; + } + + protected function getTagAttributes() { + require_celerity_resource('phui-head-thing-view-css'); + + $classes = array(); + $classes[] = 'phui-head-thing-view'; + + if ($this->size) { + $classes[] = $this->size; + } else { + $classes[] = self::SMALL; + } + + return array( + 'class' => $classes, + ); + } + + protected function getTagContent() { + + $image = phutil_tag( + 'a', + array( + 'class' => 'phui-head-thing-image', + 'style' => 'background-image: url('.$this->image.');', + 'href' => $this->imageHref, + )); + + return array($image, $this->content); + + + } + +} diff --git a/src/view/phui/PHUIHeaderView.php b/src/view/phui/PHUIHeaderView.php index 233e5a7c72..0e12bc9abe 100644 --- a/src/view/phui/PHUIHeaderView.php +++ b/src/view/phui/PHUIHeaderView.php @@ -253,8 +253,9 @@ final class PHUIHeaderView extends AphrontTagView { $left = array(); $right = array(); + $space_header = null; if ($viewer) { - $left[] = id(new PHUISpacesNamespaceContextView()) + $space_header = id(new PHUISpacesNamespaceContextView()) ->setUser($viewer) ->setObject($this->policyObject); } @@ -312,10 +313,11 @@ final class PHUIHeaderView extends AphrontTagView { $action_list); } + $icon = null; if ($this->headerIcon) { $icon = id(new PHUIIconView()) - ->setIcon($this->headerIcon); - $left[] = $icon; + ->setIcon($this->headerIcon) + ->addClass('phui-header-icon'); } $header_content = $this->header; @@ -335,7 +337,11 @@ final class PHUIHeaderView extends AphrontTagView { array( 'class' => 'phui-header-header', ), - $header_content); + array( + $space_header, + $icon, + $header_content, + )); if ($this->subheader || $this->badges) { $badges = null; diff --git a/src/view/phui/PHUIObjectBoxView.php b/src/view/phui/PHUIObjectBoxView.php index 5cbb3a58b6..9fa280a843 100644 --- a/src/view/phui/PHUIObjectBoxView.php +++ b/src/view/phui/PHUIObjectBoxView.php @@ -36,6 +36,7 @@ final class PHUIObjectBoxView extends AphrontTagView { const COLOR_YELLOW = 'yellow'; const BLUE = 'phui-box-blue'; + const BLUE_PROPERTY = 'phui-box-blue-property'; const GREY = 'phui-box-grey'; public function addPropertyList( diff --git a/src/view/phui/PHUIStatusItemView.php b/src/view/phui/PHUIStatusItemView.php index dae7e7d472..1e859f5fd2 100644 --- a/src/view/phui/PHUIStatusItemView.php +++ b/src/view/phui/PHUIStatusItemView.php @@ -83,17 +83,15 @@ final class PHUIStatusItemView extends AphrontTagView { } } - $icon_cell = phutil_tag( - 'td', - array(), - $icon); - $target_cell = phutil_tag( 'td', array( 'class' => 'phui-status-item-target', ), - $this->target); + array( + $icon, + $this->target, + )); $note_cell = phutil_tag( 'td', @@ -103,7 +101,6 @@ final class PHUIStatusItemView extends AphrontTagView { $this->note); return array( - $icon_cell, $target_cell, $note_cell, ); diff --git a/src/view/phui/PHUITwoColumnView.php b/src/view/phui/PHUITwoColumnView.php index 5a47dc925a..fb27a1a50d 100644 --- a/src/view/phui/PHUITwoColumnView.php +++ b/src/view/phui/PHUITwoColumnView.php @@ -3,10 +3,14 @@ final class PHUITwoColumnView extends AphrontTagView { private $mainColumn; - private $sideColumn; + private $sideColumn = null; private $display; private $fluid; private $header; + private $subheader; + private $propertySection = array(); + private $actionList; + private $propertyList; const DISPLAY_LEFT = 'phui-side-column-left'; const DISPLAY_RIGHT = 'phui-side-column-right'; @@ -26,6 +30,26 @@ final class PHUITwoColumnView extends AphrontTagView { return $this; } + public function setSubheader($subheader) { + $this->subheader = $subheader; + return $this; + } + + public function addPropertySection($title, $section) { + $this->propertySection[] = array($title, $section); + return $this; + } + + public function setActionList(PhabricatorActionListView $list) { + $this->actionList = $list; + return $this; + } + + public function setPropertyList(PHUIPropertyListView $list) { + $this->propertyList = $list; + return $this; + } + public function setFluid($fluid) { $this->fluid = $fluid; return $this; @@ -53,6 +77,10 @@ final class PHUITwoColumnView extends AphrontTagView { $classes[] = 'phui-two-column-fluid'; } + if ($this->subheader) { + $classes[] = 'with-subheader'; + } + return array( 'class' => implode(' ', $classes), ); @@ -61,32 +89,26 @@ final class PHUITwoColumnView extends AphrontTagView { protected function getTagContent() { require_celerity_resource('phui-two-column-view-css'); - $main = phutil_tag( - 'div', - array( - 'class' => 'phui-main-column', - ), - $this->mainColumn); + $main = $this->buildMainColumn(); + $side = $this->buildSideColumn(); + $order = array($side, $main); - $side = phutil_tag( - 'div', - array( - 'class' => 'phui-side-column', - ), - $this->sideColumn); - - if ($this->getDisplay() == self::DISPLAY_LEFT) { - $order = array($side, $main); - } else { - $order = array($main, $side); - } - - $inner = phutil_tag_div('phui-two-column-row', $order); + $inner = phutil_tag_div('phui-two-column-row grouped', $order); $table = phutil_tag_div('phui-two-column-content', $inner); $header = null; if ($this->header) { - $header = phutil_tag_div('phui-two-column-header', $this->header); + if ($this->actionList) { + $this->header->setActionList($this->actionList); + } + $header = phutil_tag_div( + 'phui-two-column-header', $this->header); + } + + $subheader = null; + if ($this->subheader) { + $subheader = phutil_tag_div( + 'phui-two-column-subheader', $this->subheader); } return phutil_tag( @@ -96,7 +118,62 @@ final class PHUITwoColumnView extends AphrontTagView { ), array( $header, + $subheader, $table, )); } + + private function buildMainColumn() { + + $view = array(); + $sections = $this->propertySection; + + if ($sections) { + foreach ($sections as $content) { + if ($content[1]) { + $view[] = id(new PHUIObjectBoxView()) + ->setHeaderText($content[0]) + ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) + ->appendChild($content[1]); + } + } + } + + return phutil_tag( + 'div', + array( + 'class' => 'phui-main-column', + ), + array( + $view, + $this->mainColumn, + )); + } + + private function buildSideColumn() { + $property_list = $this->propertyList; + $action_list = $this->actionList; + + $properties = null; + if ($property_list || $action_list) { + if ($property_list) { + $property_list->setStacked(true); + } + + $properties = id(new PHUIObjectBoxView()) + ->appendChild($action_list) + ->appendChild($property_list) + ->addClass('phui-two-column-properties'); + } + + return phutil_tag( + 'div', + array( + 'class' => 'phui-side-column', + ), + array( + $properties, + $this->sideColumn, + )); + } } diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index b235fd9c47..b2efc8c3c0 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -15,7 +15,15 @@ border-bottom: 1px solid {$blueborder}; } -.aphront-table-view td.aphront-table-notice { +.aphront-table-view-fixed { + table-layout: fixed; +} + +.aphront-table-view-fixed th { + box-sizing: border-box; +} + +.aphront-table-notice { padding: 12px 16px; font-size: {$normalfontsize}; color: {$darkbluetext}; @@ -240,10 +248,11 @@ span.single-display-line-content { } .aphront-table-view tr.no-data td { - padding: 12px; + padding: 16px; text-align: center; color: {$lightgreytext}; font-style: italic; + font-size: {$normalfontsize}; } .aphront-table-view td.thumb img { diff --git a/webroot/rsrc/css/application/countdown/timer.css b/webroot/rsrc/css/application/countdown/timer.css index 8b87aa6ed2..bbb2fdd684 100644 --- a/webroot/rsrc/css/application/countdown/timer.css +++ b/webroot/rsrc/css/application/countdown/timer.css @@ -4,9 +4,6 @@ .phabricator-timer { margin: 16px 16px 0 16px; - border: 1px solid {$lightblueborder}; - border-radius: 3px; - background: #ffffff; } .device .phabricator-timer { @@ -14,27 +11,20 @@ margin-right: 8px; } -.phabricator-remarkup .phabricator-timer { - max-width: 360px; +.phabricator-remarkup .phabricator-timer-view { + max-width: 460px; margin: 0 0 12px 0; } +.phabricator-timer .countdown-description { + border-bottom: 1px solid {$thinblueborder}; + padding-bottom: 16px; +} + .device-phone .phabricator-remarkup .phabricator-timer { width: auto; } -.phabricator-timer-header { - font-weight: bold; - padding: 8px; - background: {$bluebackground}; - border-radius: 3px 3px 0 0; -} - -.phabricator-timer-header a { - color: {$darkbluetext}; - font-weight: normal; -} - .phabricator-timer-table { width: 100%; margin: 8px 0 0 0; @@ -60,12 +50,11 @@ } .phabricator-timer-table td.phabricator-timer-foot { - font-size: {$normalfontsize}; + font-size: {$biggerfontsize}; line-height: 16px; border-top: 1px solid {$thinblueborder}; color: {$bluetext}; - font-weight: normal; - padding: 8px; + padding: 12px 0; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; text-align: center; diff --git a/webroot/rsrc/css/application/harbormaster/harbormaster.css b/webroot/rsrc/css/application/harbormaster/harbormaster.css index 6158582ddd..a80c8b7418 100644 --- a/webroot/rsrc/css/application/harbormaster/harbormaster.css +++ b/webroot/rsrc/css/application/harbormaster/harbormaster.css @@ -20,3 +20,11 @@ padding: 12px; color: {$darkgreytext}; } + +.harbormaster-unit-details { + margin: 8px 0 4px; + overflow: hidden; + white-space: pre; + text-overflow: ellipsis; + color: {$lightgreytext}; +} diff --git a/webroot/rsrc/css/application/herald/herald.css b/webroot/rsrc/css/application/herald/herald.css index 8feebd122e..26cb9cfd71 100644 --- a/webroot/rsrc/css/application/herald/herald.css +++ b/webroot/rsrc/css/application/herald/herald.css @@ -42,16 +42,16 @@ } .herald-list-description { - color: {$darkgreytext}; + color: {$bluetext}; + font-weight: bold; padding: 8px 0; } .herald-list-icon { - margin-left: 12px; - margin-right: 2px; + margin-right: 8px; } .herald-list-item { - padding-bottom: 4px; - color: {$greytext}; + padding-bottom: 20px; + color: {$darkbluetext}; } diff --git a/webroot/rsrc/css/application/paste/paste.css b/webroot/rsrc/css/application/paste/paste.css index 78bfec2587..b9b2fe2dd2 100644 --- a/webroot/rsrc/css/application/paste/paste.css +++ b/webroot/rsrc/css/application/paste/paste.css @@ -2,14 +2,6 @@ * @provides paste-css */ -.container-of-paste { - margin: 16px 16px 0 16px; -} - -.device .container-of-paste { - margin: 8px 8px 0 8px; -} - .paste-embed { background: {$sh-yellowbackground}; border: 1px solid {$sh-lightyellowborder}; diff --git a/webroot/rsrc/css/application/ponder/ponder-view.css b/webroot/rsrc/css/application/ponder/ponder-view.css index a5bd01a6b1..966fb6a1fb 100644 --- a/webroot/rsrc/css/application/ponder/ponder-view.css +++ b/webroot/rsrc/css/application/ponder/ponder-view.css @@ -2,40 +2,10 @@ * @provides ponder-view-css */ -.ponder-question-view { - background: #fff; - padding-bottom: 64px; -} - -.device-desktop .ponder-question-view.phui-two-column-view .phui-side-column { - width: 300px; -} - -.ponder-question-view .phui-object-box, -.ponder-question-view .phui-info-view { - margin-left: 0; - margin-right: 0; -} - -.device-phone .ponder-question-view .phui-profile-header.phui-header-shell - .phui-header-header { - font-size: 20px; -} - .ponder-question-container { border-top: 1px solid {$thinblueborder}; } -.ponder-question-content { - margin: 0 24px; - padding: 24px 0; - border-top: 1px solid rgba({$alphagrey}, .15); -} - -.device-phone .ponder-question-content { - margin: 0 16px; -} - .device .ponder-view-properties { border-left: none; border-right: none; @@ -57,59 +27,19 @@ border: none; } -.ponder-view-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-key { - padding: 0; +.phui-two-column-view .phui-main-column .ponder-question-content + .phui-box.ponder-answer-wiki { + margin: 24px 0; } -.ponder-view-properties .phui-property-list-stacked - .phui-property-list-properties .phui-property-list-value { - margin-bottom: 16px; - padding: 0; -} - -.phui-box.ponder-answer-wiki { +.ponder-answer-wiki .phabricator-remarkup { padding: 16px; - margin: 24px 0; -} - -.ponder-details-subtitle { - height: 24px; - line-height: 24px; - margin-bottom: 12px; - color: {$greytext}; - position: relative; - padding-left: 32px; -} - -.ponder-details-subtitle a { - color: {$darkgreytext}; -} - -.ponder-details-author-image { - height: 24px; - width: 24px; - background-size: 100%; - margin-right: 8px; - border-radius: 3px; - display: inline-block; - position: absolute; - top: 0; - left: 0; -} - -.ponder-detail-view .phabricator-remarkup { - margin-left: 32px; } .ponder-question-content .phui-timeline-view { padding-right: 0; } -.ponder-question-content .phui-timeline-view .phui-timeline-core-content { - background-color: {$lightbluebackground}; -} - .ponder-answer-view { margin-top: 16px; } @@ -123,7 +53,7 @@ margin-left: 12px; } -.ponder-question-view .ponder-answer-view .phui-header-shell { +.ponder-question-view .ponder-answer .phui-header-shell { padding: 4px 8px 3px 8px; } @@ -187,11 +117,12 @@ body .phui-main-column .ponder-question-content .ponder-answer-view } .ponder-answer-section { - margin-top: 32px; + margin-top: 48px; } .ponder-add-answer-header { margin-top: 64px; + margin-bottom: 20px; } .ponder-add-answer-view { @@ -200,9 +131,8 @@ body .phui-main-column .ponder-question-content .ponder-answer-view .ponder-question-content div.ponder-question-add-comment-view div.phui-box.phui-object-box { - background: {$lightbluebackground}; margin-right: 0; - margin-left: 32px; + margin-left: 62px; } .device .ponder-question-content div.ponder-question-add-comment-view @@ -210,19 +140,6 @@ body .phui-main-column .ponder-question-content .ponder-answer-view margin: 0; } -.ponder-add-answer-view .phui-form-full-width.phui-form-view - label.aphront-form-label, -.ponder-question-add-comment-view .phui-form-full-width.phui-form-view - label.aphront-form-label{ - display: none; -} - -.ponder-add-answer-view.phui-box-grey .phui-header-shell { - border: none; - padding-bottom: 8px; -} - -.ponder-add-answer-view .remarkup-assist-textarea, .ponder-question-add-comment-view .remarkup-assist-textarea { height: 8em; } diff --git a/webroot/rsrc/css/application/project/project-view.css b/webroot/rsrc/css/application/project/project-view.css index be3e8b67f4..2c0fd7d6d8 100644 --- a/webroot/rsrc/css/application/project/project-view.css +++ b/webroot/rsrc/css/application/project/project-view.css @@ -2,11 +2,6 @@ * @provides project-view-css */ -.project-view-home { - padding-bottom: 64px; - background: #fff; -} - .project-view-header-tag { margin-left: 8px; font-size: {$normalfontsize}; @@ -24,25 +19,35 @@ color: {$bluetext}; } -.project-view-home .phui-box.project-view-properties { - margin: 0 16px 16px 16px; - padding: 4px 12px; - border: 2px solid rgba({$alphagrey},.1); - background-color: #F7F7F9; +.device .project-view-home .phui-two-column-row { + display: flex; + flex-direction: column-reverse; } +.project-view-home .phui-box.project-view-properties { + margin: 0 0 16px 0; + padding: 0; + border: 1px solid rgba({$alphagrey}, .2); + background-color: #fff; +} + +.device-desktop .phui-two-column-view .project-view-properties + .phui-property-list-container { + padding: 12px 0; + } + .device-phone .phui-box.project-view-properties { margin: 0 12px 0 12px; } .project-view-properties .phui-property-list-container + .phui-property-list-text-content { - border-color: rgba({$alphagrey},.15); + border-color: rgba({$alphagrey},.2); } .project-view-properties .phui-property-list-key { width: auto; - margin-left: 4px2 + margin-left: 4px; } .project-view-properties .phui-property-list-section-header { @@ -50,23 +55,8 @@ padding: 12px 4px 0; } -.project-view-feed .phui-object-box.phui-box-border { - padding: 0 4px 8px 4px; - border: none; -} - -.project-view-feed .phui-object-box .phui-header-shell { - padding: 8px 4px; -} - -.project-view-feed .phui-header-header { - font-size: {$biggerfontsize}; - margin-left: 8px; -} - -.device-desktop .project-view-feed .phui-feed-story, -.device-tablet .project-view-feed .phui-feed-story { - padding-left: 22px; +.project-view-feed.phui-object-box.phui-box-border { + border: 1px solid rgba({$alphagrey}, .2); } .project-view-home .phui-box-grey { @@ -81,13 +71,17 @@ font-size: {$biggerfontsize}; } +.project-view-home .phui-box-grey .phui-header-action-link { + margin-top: 0; + margin-bottom: 0; +} + .project-view-home .phui-box-grey .phui-object-item-list-view { padding: 4px 8px 0 8px; } .project-view-badges .phui-badge-flex-view { background-color: #fff; - width: 340px; } .project-view-home .phui-box-grey .phui-object-item-attribute .phui-icon-view { diff --git a/webroot/rsrc/css/application/slowvote/slowvote.css b/webroot/rsrc/css/application/slowvote/slowvote.css index afa45a8032..907ff0c242 100644 --- a/webroot/rsrc/css/application/slowvote/slowvote.css +++ b/webroot/rsrc/css/application/slowvote/slowvote.css @@ -2,22 +2,8 @@ * @provides phabricator-slowvote-css */ -.slowvote-embed { - background: #fff; - border-color: {$lightblueborder}; - border-radius: 3px; -} - -.slowvote-header { - font-weight: bold; - line-height: 16px; - border-bottom: 1px solid #bbbbbb; -} - .slowvote-description { - color: {$greytext}; - font-size: {$biggerfontsize}; - padding: 12px 4px 4px; + padding: 12px 16px 4px; } .slowvote-header-content { @@ -25,7 +11,7 @@ } .slowvote-body-content { - padding: 8px 4px 0; + padding: 8px 16px; } .slowvote-option-label { @@ -121,7 +107,7 @@ } .slowvote-footer-content { - padding: 8px; + padding: 8px 16px; overflow: hidden; } diff --git a/webroot/rsrc/css/phui/phui-box.css b/webroot/rsrc/css/phui/phui-box.css index 1516285cf8..7eaca8b741 100644 --- a/webroot/rsrc/css/phui/phui-box.css +++ b/webroot/rsrc/css/phui/phui-box.css @@ -15,7 +15,7 @@ .phui-box-grey { background-color: #F7F7F9; border-radius: 3px; - border-color: rgba({$alphagrey},.1); + border-color: rgba({$alphagrey},.2); } .phui-box-blue { @@ -55,3 +55,38 @@ border: none; color: {$greytext}; } + + +/* Property Boxes */ + +.phui-box.phui-box-blue-property { + border-radius: 3px; + border-color: {$lightblueborder}; + margin: 0; + padding: 0; +} + +.phui-box.phui-box-blue-property .phui-header-action-link { + margin-top: 0; + margin-bottom: 0; +} + +.device .phui-box.phui-box-blue-property { + padding: 0; +} + +.phui-box.phui-object-box.phui-box-blue-property .phui-header-shell { + background-color: #eff3fc; + border-top-right-radius: 3px; + border-top-left-radius: 3px; + padding: 6px 16px; +} + +.device .phui-box.phui-box-blue-property .phui-header-shell { + padding: 6px 12px; +} + +.phui-box.phui-box-blue-property .phui-header-header { + font-size: 13px; + color: {$bluetext}; +} diff --git a/webroot/rsrc/css/phui/phui-button.css b/webroot/rsrc/css/phui/phui-button.css index 0ba3007e5f..0e7cfd3ecc 100644 --- a/webroot/rsrc/css/phui/phui-button.css +++ b/webroot/rsrc/css/phui/phui-button.css @@ -26,8 +26,9 @@ button, a.button, a.button:visited, input[type="submit"] { - background-color: {$blue}; - border: 1px solid {$blue}; + background-color: #2980b9; + border: 1px solid #2980b9; + background-image: linear-gradient(to bottom, #3498db, #2980b9); color: white; cursor: pointer; font-weight: bold; @@ -53,6 +54,7 @@ a.green, a.green:visited { background-color: {$green}; border-color: {$green}; + background-image: linear-gradient(to bottom, #23BB5B, #139543); } button.grey, @@ -60,7 +62,8 @@ input[type="submit"].grey, a.grey, a.grey:visited { background-color: #F7F7F9; - border: 1px solid rgba({$alphagrey},.1); + background-image: linear-gradient(to bottom, #ffffff, #f1f0f1); + border: 1px solid rgba({$alphablue},.2); color: {$darkgreytext}; } @@ -100,26 +103,31 @@ a.phuix-dropdown-open { a.button:hover, button:hover { text-decoration: none; - background-color: {$sky}; + background-color: #2980b9; + background-image: linear-gradient(to bottom, #3498db, #1b6ba0); + border-color: #115988; transition: 0.1s; } a.button.grey:hover, button.grey:hover { - background-color: rgba({$alphablue}, 0.1); - border-color: rgba({$alphablue}, 0.15); + background-image: linear-gradient(to bottom, #ffffff, #eeebec); + border-color: rgba({$alphablue}, 0.3); transition: 0.1s; } a.button.green:hover, button.green:hover { + border-color: #127336; background-color: #0DAD48; + background-image: linear-gradient(to bottom, #23BB5B, #178841); transition: 0.1s; } a.button.simple:hover, button.simple:hover { background-color: {$blue}; + background-image: linear-gradient(to bottom, {$blue}, {$blue}); color: #fff; transition: 0.1s; } @@ -164,6 +172,7 @@ button.link { display: inline; border: none; background: transparent; + background-image: none; font-weight: normal; padding: 0; margin: 0; @@ -309,6 +318,7 @@ a.policy-control .caret { .phui-button-bar-borderless .button { border: 0; background-color: transparent; + background-image: none; padding-left: 10px; padding-right: 10px; } @@ -320,6 +330,7 @@ a.policy-control .caret { .phui-button-bar-borderless .button:hover { background-color: transparent; + background-image: none; border-radius: 3px; } @@ -356,10 +367,12 @@ a.policy-control .caret { .phui-button-bar .button.simple:hover { border-color: {$lightblueborder}; background-color: #fff; + background-image: none; color: {$sky}; } .phui-button-bar .button.simple:hover .phui-icon-view { border-color: {$lightblueborder}; color: {$sky}; + background-image: none; } diff --git a/webroot/rsrc/css/phui/phui-head-thing.css b/webroot/rsrc/css/phui/phui-head-thing.css new file mode 100644 index 0000000000..bce67ef387 --- /dev/null +++ b/webroot/rsrc/css/phui/phui-head-thing.css @@ -0,0 +1,33 @@ +/** + * @provides phui-head-thing-view-css + */ + +.phui-head-thing-view { + height: 24px; + line-height: 22px; + color: {$greytext}; + position: relative; + padding-left: 32px; +} + +.device-phone .phui-head-thing-view { + min-height: 24px; + height: auto; + line-height: inherit; +} + +.phui-head-thing-view a { + color: {$darkgreytext}; +} + +.phui-head-thing-image { + height: 24px; + width: 24px; + background-size: 100%; + margin-right: 8px; + border-radius: 3px; + display: inline-block; + position: absolute; + top: 0; + left: 0; +} diff --git a/webroot/rsrc/css/phui/phui-header-view.css b/webroot/rsrc/css/phui/phui-header-view.css index c364059fc5..cd4b7b1238 100644 --- a/webroot/rsrc/css/phui/phui-header-view.css +++ b/webroot/rsrc/css/phui/phui-header-view.css @@ -75,6 +75,11 @@ body .phui-header-shell.phui-bleed-header color: {$darkbluetext}; } +.phui-header-header .phui-header-icon { + margin-right: 8px; + color: {$lightbluetext}; +} + .phui-object-box .phui-header-tall .phui-header-header, .phui-document-view .phui-header-tall .phui-header-header { font-size: 18px; @@ -185,7 +190,7 @@ body .phui-header-shell.phui-bleed-header .phui-header-subheader .phui-header-status { padding: 3px 9px; border-radius: 3px; - background: rgba({$alphablue}, 0.06); + background: rgba({$alphablue}, 0.08); margin-right: 8px; } @@ -272,10 +277,16 @@ body .phui-header-shell.phui-bleed-header font-size: 18px; } -.spaces-name .phui-handle { +.spaces-name .phui-handle, +.spaces-name a.phui-handle { color: {$sh-redtext}; } +.device-desktop .spaces-name a.phui-handle:hover { + color: {$sh-redtext}; + text-decoration: underline; +} + .phui-header-subheader .phui-badge-flex-view { display: inline; margin-right: 4px; diff --git a/webroot/rsrc/css/phui/phui-object-box.css b/webroot/rsrc/css/phui/phui-object-box.css index d7452c2126..f9f06e2d18 100644 --- a/webroot/rsrc/css/phui/phui-object-box.css +++ b/webroot/rsrc/css/phui/phui-object-box.css @@ -120,6 +120,10 @@ div.phui-object-box.phui-object-box-flush { margin: 0; } +.phui-object-box .phui-box-border.phui-box-blue-property { + border-width: 1px; +} + .phui-object-box .phui-object-box .phui-header-shell .phui-header-header { font-size: {$normalfontsize}; margin: 0; diff --git a/webroot/rsrc/css/phui/phui-status.css b/webroot/rsrc/css/phui/phui-status.css index 9dc446d536..d8f46c002f 100644 --- a/webroot/rsrc/css/phui/phui-status.css +++ b/webroot/rsrc/css/phui/phui-status.css @@ -7,10 +7,9 @@ } .phui-status-list-view .phui-icon-view { - display: block; width: 14px; height: 14px; - margin: 3px 4px; + margin: 2px 4px 2px 0; } .phui-status-item-target { diff --git a/webroot/rsrc/css/phui/phui-two-column-view.css b/webroot/rsrc/css/phui/phui-two-column-view.css index 6e0880a4d9..6c36aa8bd1 100644 --- a/webroot/rsrc/css/phui/phui-two-column-view.css +++ b/webroot/rsrc/css/phui/phui-two-column-view.css @@ -2,57 +2,208 @@ * @provides phui-two-column-view-css */ -.phui-two-column-view { +.phui-two-column-view .phui-two-column-header { background-color: #fff; + border-bottom: 1px solid rgba({$alphagrey}, .12); + margin-bottom: 24px; } -.phui-two-column-container { - max-width: 1024px; - margin: 0 auto; +.phui-two-column-view.with-subheader .phui-two-column-header { + margin-bottom: 0; +} + +.phui-two-column-header .phui-header-header { + font-size: 20px; + font-family: 'Aleo', {$fontfamily}; + color: #000; +} + +.device-phone .phui-two-column-header .phui-header-header { + font-size: 18px; } .phui-two-column-view .phui-two-column-header .phui-header-shell { - padding-bottom: 32px; + padding: 24px 32px 28px; + border: none; } -.device-phone .phui-two-column-view .phui-two-column-header .phui-header-shell { - padding-bottom: 20px; +.phui-two-column-view .phui-two-column-header + .phui-profile-header.phui-header-shell { + padding-bottom: 20px; } -.phui-two-column-fluid .phui-two-column-container { - max-width: 100%; +.device .phui-two-column-view .phui-two-column-header .phui-header-shell { + padding: 12px 12px 16px; } -.phui-two-column-content { - display: table; - width: 100%; +.phui-two-column-header .phui-header-subheader { + margin-top: 12px; } -.phui-two-column-row { - display: table-row; +.phui-two-column-subheader { + padding: 12px 32px; +} + +.device .phui-two-column-subheader { + padding: 12px 16px; +} + +.device-desktop .phui-two-column-content { + padding: 0 32px; +} + +.device .phui-two-column-content { + padding: 0 12px; } .device-desktop .phui-two-column-view .phui-main-column { - display: table-cell; - vertical-align: top; + float: left; + width: calc(100% - 320px); } .device-desktop .phui-two-column-view .phui-side-column { - width: 320px; - display: table-cell; - vertical-align: top; + float: right; + width: 300px; } -.device-desktop .phui-two-column-view - .phui-main-column .phui-object-box:first-child { - margin: 0 16px 0 16px; +.device .phui-side-column { + margin-bottom: 20px; } -.device-desktop .phui-two-column-view - .phui-side-column .phui-object-box { - margin: 0 16px 16px 0; +.phui-two-column-view .phui-two-column-content + .phui-object-box { + margin: 0 0 20px 0; } -.phui-two-column-view pre { - white-space: pre-wrap; +.phui-two-column-view .phui-object-box.phui-object-box-collapsed { + padding: 0; +} + +/* Timeline */ + +.phui-two-column-view .phui-timeline-view { + padding: 0; + background-position: 78px; +} + +.phui-two-column-view .phui-main-column .phui-object-box + .phui-timeline-view { + margin-top: -20px; +} + +.device .phui-two-column-view .phui-timeline-view { + background-position: 16px; + padding: 0; +} + +.device-phone .phui-two-column-view .phui-timeline-event-view { + margin: 0; +} + +/* Main Column Properties */ + +.device-desktop .phui-main-column .phui-property-list-key { + margin-left: 0; + width: 140px; +} + +.device-desktop .phui-main-column .phui-property-list-value { + margin-left: 8px; + width: calc(100% - 180px); +} + + +/* Property / Action List */ + +.phui-two-column-properties .phabricator-action-list-view { + padding-top: 4px; + padding-bottom: 12px; +} + +.device-desktop .phui-two-column-view .phui-property-list-container { + padding: 12px 16px; +} + +.device .phui-two-column-view .phui-property-list-container { + padding: 12px 8px; +} + +.phui-two-column-properties.phui-object-box { + border: 1px solid rgba({$alphablue}, .2); +} + +.phui-two-column-properties .phui-property-list-stacked + .phui-property-list-properties .phui-property-list-key { + margin: 4px 0 8px 0; + padding: 20px 4px 0; + border-top: 1px solid rgba({$alphablue}, .2); +} + +.phui-two-column-properties .phui-property-list-stacked + .phui-property-list-properties .phui-property-list-value { + margin: 0 0 20px 0; + padding: 0 4px; +} + +.device-desktop .phui-two-column-properties .phui-property-list-container { + padding: 0; +} + +.device .phui-two-column-properties .phui-property-list-stacked + .phui-property-list-properties .phui-property-list-key { + margin: 12px 0 4px 0; + padding: 0; + border: none; +} + +.device .phui-two-column-properties .phui-property-list-container { + padding: 0 0 12px 0; +} + +.device .phui-two-column-content .phui-two-column-properties.phui-object-box { + padding: 0 12px; +} + +.phui-two-column-properties .phabricator-action-view-icon { + top: 8px; + left: 8px; +} + +.phabricator-action-view button.phabricator-action-view-item, +.phabricator-action-view-item { + padding: 5px 4px 5px 28px; +} + +.device-desktop .phui-two-column-properties .phabricator-action-view:hover + .phabricator-action-view-item { + text-decoration: none; + background-color: rgba({$alphablue}, .08); + color: {$sky}; + border-radius: 3px; +} + +.device-desktop .phui-two-column-properties .phabricator-action-view:hover + .phabricator-action-view-icon { + color: {$sky}; +} + +.phui-two-column-view .phui-property-list-section-header, +.phui-two-column-view .phui-property-list-text-content { + margin: 0 16px; +} + +.device .phui-two-column-view .phui-property-list-section-header, +.device .phui-two-column-view .phui-property-list-text-content { + margin: 0 8px; +} + +/* Info View */ + +.phui-two-column-view .phui-two-column-content .phui-info-view { + margin: 0 0 20px 0; + padding: 16px; +} + +.phui-two-column-view .phui-two-column-content .phui-object-box + .phui-info-view { + margin: 0; }