From 8166f25c0d077e5a4f6a58ca7e91e9d9d7344f30 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 22 May 2015 14:30:15 -0700 Subject: [PATCH 01/43] First step towards a Javelin behavior for Calendar day view Summary: Ref T8300, First step towards a Javelin behavior for Calendar day view Test Plan: No user facing changes. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8300 Differential Revision: https://secure.phabricator.com/D12978 --- resources/celerity/map.php | 6 ++- .../phui/calendar/PHUICalendarDayView.php | 25 ++++++++++ .../application/calendar/behavior-day-view.js | 50 +++++++++++++++++++ ...t-all-day.js => behavior-event-all-day.js} | 0 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 webroot/rsrc/js/application/calendar/behavior-day-view.js rename webroot/rsrc/js/application/calendar/{event-all-day.js => behavior-event-all-day.js} (100%) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index ad88264ee5..422fee0b9d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -331,7 +331,8 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/event-all-day.js' => 'ca5fa62a', + 'rsrc/js/application/calendar/behavior-day-view.js' => 'f4f4ad80', + 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', @@ -553,6 +554,7 @@ return array( 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', + 'javelin-behavior-day-view' => 'f4f4ad80', 'javelin-behavior-device' => 'a205cf28', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', @@ -573,7 +575,7 @@ return array( 'javelin-behavior-doorkeeper-tag' => 'e5822781', 'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-error-log' => '6882e80a', - 'javelin-behavior-event-all-day' => 'ca5fa62a', + 'javelin-behavior-event-all-day' => '38dcf3c8', 'javelin-behavior-fancy-datepicker' => '5c0f680f', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 337ef50738..7a96100b2a 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -10,6 +10,7 @@ final class PHUICalendarDayView extends AphrontView { private $browseURI; private $events = array(); private $todayEvents = array(); + private $jsTodayEvents = array(); private $allDayEvents = array(); @@ -45,6 +46,7 @@ final class PHUICalendarDayView extends AphrontView { require_celerity_resource('phui-calendar-day-css'); $hours = $this->getHoursOfDay(); + $js_hourly_events = array(); $hourly_events = array(); $first_event_hour = null; @@ -83,6 +85,11 @@ final class PHUICalendarDayView extends AphrontView { && $event->getEpochStart() < $hour_end)) { $current_hour_events[] = $event; $this->todayEvents[] = $event; + $this->jsTodayEvents[] = array( + 'eventStartEpoch' => $event->getEpochStart(), + 'eventEndEpoch' => $event->getEpochEnd(), + 'eventName' => $event->getName(), + ); } } foreach ($current_hour_events as $event) { @@ -102,6 +109,17 @@ final class PHUICalendarDayView extends AphrontView { $first_event_hour = $hour; } + $js_hourly_events[$event->getEventID()] = array( + 'eventStartEpoch' => $event->getEpochStart(), + 'eventEndEpoch' => $event->getEpochEnd(), + 'eventName' => $event->getName(), + 'hour' => $hour, + 'offset' => '0', + 'width' => '100%', + 'top' => $top.'%', + 'height' => $height.'%', + ); + $hourly_events[$event->getEventID()] = array( 'hour' => $hour, 'event' => $event, @@ -176,6 +194,13 @@ final class PHUICalendarDayView extends AphrontView { $sidebar = $this->renderSidebar(); $warnings = $this->getQueryRangeWarning(); + Javelin::initBehavior( + 'day-view', + array( + 'todayEvents' => $this->jsTodayEvents, + 'hourlyEvents' => $js_hourly_events, + )); + $table_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->appendChild($all_day_event_box) diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js new file mode 100644 index 0000000000..9cd90e01bc --- /dev/null +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -0,0 +1,50 @@ +/** + * @provides javelin-behavior-day-view + */ + + +JX.behavior('day-view', function(config) { + var hourly_events = config.hourlyEvents; + var today_events = config.todayEvents; + + function findTodayClusters() { + var events = today_events.sort(function(x, y){ + return (x.eventStartEpoch - y.eventStartEpoch); + }); + + var clusters = []; + + events.forEach(function(e){ + var destination_cluster_index = null; + var event_start = e.eventStartEpoch - (30*60); + var event_end = e.eventEndEpoch + (30*60); + + clusters.forEach(function(cluster, index){ + for(var i=0; i < cluster.length; i++) { + var clustered_event = cluster[i]; + var compare_event_start = clustered_event.eventStartEpoch; + var compare_event_end = clustered_event.eventEndEpoch; + + if (event_start < compare_event_end && + event_end > compare_event_start) { + destination_cluster_index = index; + break; + } + } + }); + + if (destination_cluster_index !== null) { + clusters[destination_cluster_index].push(e); + destination_cluster_index = null; + } else { + var next_cluster = []; + next_cluster.push(e); + clusters.push(next_cluster); + } + }); + + return clusters; + } + + var today_clusters = findTodayClusters(); +}); diff --git a/webroot/rsrc/js/application/calendar/event-all-day.js b/webroot/rsrc/js/application/calendar/behavior-event-all-day.js similarity index 100% rename from webroot/rsrc/js/application/calendar/event-all-day.js rename to webroot/rsrc/js/application/calendar/behavior-event-all-day.js From 04584131f79159376105c23b9bfb9d76249467f9 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 22 May 2015 14:46:48 -0700 Subject: [PATCH 02/43] Swapping out foreach out of behavior Summary: Ref T8300, swapping out foreach out of behavior Test Plan: no user facing changes Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8300 Differential Revision: https://secure.phabricator.com/D12980 --- .../application/calendar/behavior-day-view.js | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index 9cd90e01bc..f527de748f 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -7,6 +7,7 @@ JX.behavior('day-view', function(config) { var hourly_events = config.hourlyEvents; var today_events = config.todayEvents; + function findTodayClusters() { var events = today_events.sort(function(x, y){ return (x.eventStartEpoch - y.eventStartEpoch); @@ -14,34 +15,42 @@ JX.behavior('day-view', function(config) { var clusters = []; - events.forEach(function(e){ - var destination_cluster_index = null; - var event_start = e.eventStartEpoch - (30*60); - var event_end = e.eventEndEpoch + (30*60); + for (var i=0; i < events.length; i++) { + var today_event = events[i]; - clusters.forEach(function(cluster, index){ - for(var i=0; i < cluster.length; i++) { - var clustered_event = cluster[i]; + var destination_cluster_index = null; + var event_start = today_event.eventStartEpoch - (30*60); + var event_end = today_event.eventEndEpoch + (30*60); + + for (var j=0; j < clusters.length; j++) { + var cluster = clusters[j]; + + for(var k=0; k < cluster.length; k++) { + var clustered_event = cluster[k]; var compare_event_start = clustered_event.eventStartEpoch; var compare_event_end = clustered_event.eventEndEpoch; if (event_start < compare_event_end && event_end > compare_event_start) { - destination_cluster_index = index; + destination_cluster_index = j; break; } } - }); + + if (destination_cluster_index !== null) { + break; + } + } if (destination_cluster_index !== null) { - clusters[destination_cluster_index].push(e); + clusters[destination_cluster_index].push(today_event); destination_cluster_index = null; } else { var next_cluster = []; - next_cluster.push(e); + next_cluster.push(today_event); clusters.push(next_cluster); } - }); + } return clusters; } From e032bab9efda65f9fc4c420b5d1cd1b915294ebc Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 22 May 2015 15:30:17 -0700 Subject: [PATCH 03/43] Calculate event offsets in js Summary: Ref T8300, Calculate event offsets in js Test Plan: no user facing changes Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8300 Differential Revision: https://secure.phabricator.com/D12981 --- .../phui/calendar/PHUICalendarDayView.php | 4 ++- .../application/calendar/behavior-day-view.js | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 7a96100b2a..cbf1023f61 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -89,6 +89,7 @@ final class PHUICalendarDayView extends AphrontView { 'eventStartEpoch' => $event->getEpochStart(), 'eventEndEpoch' => $event->getEpochEnd(), 'eventName' => $event->getName(), + 'eventID' => $event->getEventID(), ); } } @@ -109,10 +110,11 @@ final class PHUICalendarDayView extends AphrontView { $first_event_hour = $hour; } - $js_hourly_events[$event->getEventID()] = array( + $js_hourly_events[] = array( 'eventStartEpoch' => $event->getEpochStart(), 'eventEndEpoch' => $event->getEpochEnd(), 'eventName' => $event->getName(), + 'eventID' => $event->getEventID(), 'hour' => $hour, 'offset' => '0', 'width' => '100%', diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index f527de748f..bcd87912cc 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -55,5 +55,31 @@ JX.behavior('day-view', function(config) { return clusters; } + function updateEventsFromCluster(cluster, hourly_events) { + var cluster_size = cluster.length; + var n = 0; + for(var i=0; i < cluster.length; i++) { + var cluster_member = cluster[i]; + + var event_id = cluster_member.eventID; + var offset = ((n / cluster_size) * 100) + '%'; + var width = ((1 / cluster_size) * 100) + '%'; + + for (var j=0; j < hourly_events.length; j++) { + if (hourly_events[j].eventID == event_id) { + + hourly_events[j]['offset'] = offset; + hourly_events[j]['width'] = width; + } + } + n++; + } + + return hourly_events; + } + var today_clusters = findTodayClusters(); + for(var i=0; i < today_clusters.length; i++) { + hourly_events = updateEventsFromCluster(today_clusters[i], hourly_events); + } }); From 07a2bb7f3b0c28e91e8283e6b6cc6ef7155ebde2 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 22 May 2015 16:26:48 -0700 Subject: [PATCH 04/43] Translate drawing day rows into javascript Summary: Ref T8300, Translate drawing day rows into javascript Test Plan: no user facing changes Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8300 Differential Revision: https://secure.phabricator.com/D12983 --- .../phui/calendar/PHUICalendarDayView.php | 17 +++- .../application/calendar/behavior-day-view.js | 97 +++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index cbf1023f61..7d32866cea 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -46,6 +46,15 @@ final class PHUICalendarDayView extends AphrontView { require_celerity_resource('phui-calendar-day-css'); $hours = $this->getHoursOfDay(); + $js_hours = array(); + + foreach ($hours as $hour) { + $js_hours[] = array( + 'hour' => $hour->format('G'), + 'hour_meridian' => $hour->format('g A'), + ); + } + $js_hourly_events = array(); $hourly_events = array(); @@ -90,6 +99,8 @@ final class PHUICalendarDayView extends AphrontView { 'eventEndEpoch' => $event->getEpochEnd(), 'eventName' => $event->getName(), 'eventID' => $event->getEventID(), + 'viewerIsInvited' => $event->getViewerIsInvited(), + 'uri' => $event->getURI(), ); } } @@ -115,7 +126,9 @@ final class PHUICalendarDayView extends AphrontView { 'eventEndEpoch' => $event->getEpochEnd(), 'eventName' => $event->getName(), 'eventID' => $event->getEventID(), - 'hour' => $hour, + 'viewerIsInvited' => $event->getViewerIsInvited(), + 'uri' => $event->getURI(), + 'hour' => $hour->format('G'), 'offset' => '0', 'width' => '100%', 'top' => $top.'%', @@ -201,6 +214,8 @@ final class PHUICalendarDayView extends AphrontView { array( 'todayEvents' => $this->jsTodayEvents, 'hourlyEvents' => $js_hourly_events, + 'hours' => $js_hours, + 'firstEventHour' => $first_event_hour->format('G'), )); $table_box = id(new PHUIObjectBoxView()) diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index bcd87912cc..f37714c04f 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -4,6 +4,8 @@ JX.behavior('day-view', function(config) { + var hours = config.hours; + var first_event_hour = config.firstEventHour; var hourly_events = config.hourlyEvents; var today_events = config.todayEvents; @@ -78,8 +80,103 @@ JX.behavior('day-view', function(config) { return hourly_events; } + function drawEvent( + start, + end, + name, + viewerIsInvited, + uri, + id, + offset, + width, + top, + height) { + + var link_class = 'phui-calendar-day-event-link'; + if (viewerIsInvited) { + link_class = link_class + ' viewer-invited-day-event'; + } + + var name_link = JX.$N( + 'a', + { + className : link_class, + href: uri + }, + name); + + var div = JX.$N( + 'div', + { + className: 'phui-calendar-day-event', + style: { + left: offset, + width: width, + top: top, + height: height + } + }, + name_link); + + return div; + } + + function drawRows() { + var rows = []; + var early_hours = [8]; + if (first_event_hour) { + early_hours.push(first_event_hour); + } + var min_early_hour = Math.min(early_hours[0], early_hours[1]); + + + for(var i=0; i < hours.length; i++) { + if (hours[i]['hour'] < min_early_hour) { + continue; + } + var drawn_hourly_events = []; + var cell_time = JX.$N( + 'td', + {className: 'phui-calendar-day-hour'}, + hours[i]['hour_meridian']); + + for (var j=0; j < hourly_events.length; j++) { + if (hourly_events[j]['hour'] == hours[i]['hour']) { + drawn_hourly_events.push( + drawEvent( + hourly_events[j]['eventStartEpoch'], + hourly_events[j]['eventEndEpoch'], + hourly_events[j]['eventName'], + hourly_events[j]['eventID'], + hourly_events[j]['viewerIsInvited'], + hourly_events[j]['hour'], + hourly_events[j]['offset'], + hourly_events[j]['width'], + hourly_events[j]['top'], + hourly_events[j]['height'] + ) + ); + } + } + + var cell_event = JX.$N( + 'td', + { + className: 'phui-calendar-day-events' + }, + drawn_hourly_events); + var row = JX.$N( + 'tr', + {}, + [cell_time, cell_event]); + rows.push(row); + } + return rows; + } + var today_clusters = findTodayClusters(); for(var i=0; i < today_clusters.length; i++) { hourly_events = updateEventsFromCluster(today_clusters[i], hourly_events); } + var rows = drawRows(); }); From 6481884d26d357d60150b711c7be54f41b19b85e Mon Sep 17 00:00:00 2001 From: lkassianik Date: Fri, 22 May 2015 17:50:23 -0700 Subject: [PATCH 05/43] Translating day view into javascript, actually. Summary: Ref T8300, Translating day view into javascript, actually Test Plan: should be no user facing changes. should look the same as it did before. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8300 Differential Revision: https://secure.phabricator.com/D12985 --- .../phui/calendar/PHUICalendarDayView.php | 167 +++--------------- .../application/calendar/behavior-day-view.js | 83 ++++++--- 2 files changed, 79 insertions(+), 171 deletions(-) diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 7d32866cea..30d35e93fd 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -9,7 +9,6 @@ final class PHUICalendarDayView extends AphrontView { private $year; private $browseURI; private $events = array(); - private $todayEvents = array(); private $jsTodayEvents = array(); private $allDayEvents = array(); @@ -55,13 +54,12 @@ final class PHUICalendarDayView extends AphrontView { ); } - $js_hourly_events = array(); - $hourly_events = array(); - $first_event_hour = null; + $js_hourly_events = array(); + $js_today_all_day_events = array(); + $all_day_events = $this->getAllDayEvents(); - $today_all_day_events = array(); $day_start = $this->getDateTime(); $day_end = id(clone $day_start)->modify('+1 day'); @@ -74,7 +72,12 @@ final class PHUICalendarDayView extends AphrontView { $all_day_end = $all_day_event->getEpochEnd(); if ($all_day_start < $day_end_epoch && $all_day_end > $day_start_epoch) { - $today_all_day_events[] = $all_day_event; + $js_today_all_day_events[] = array( + 'name' => $all_day_event->getName(), + 'id' => $all_day_event->getEventID(), + 'viewerIsInvited' => $all_day_event->getViewerIsInvited(), + 'uri' => $all_day_event->getURI(), + ); } } @@ -93,7 +96,6 @@ final class PHUICalendarDayView extends AphrontView { ($event->getEpochStart() >= $hour_start && $event->getEpochStart() < $hour_end)) { $current_hour_events[] = $event; - $this->todayEvents[] = $event; $this->jsTodayEvents[] = array( 'eventStartEpoch' => $event->getEpochStart(), 'eventEndEpoch' => $event->getEpochEnd(), @@ -134,94 +136,36 @@ final class PHUICalendarDayView extends AphrontView { 'top' => $top.'%', 'height' => $height.'%', ); - - $hourly_events[$event->getEventID()] = array( - 'hour' => $hour, - 'event' => $event, - 'offset' => '0', - 'width' => '100%', - 'top' => $top.'%', - 'height' => $height.'%', - ); } } - $clusters = $this->findTodayClusters(); - foreach ($clusters as $cluster) { - $hourly_events = $this->updateEventsFromCluster( - $cluster, - $hourly_events); - } - - $rows = array(); - - foreach ($hours as $hour) { - $early_hours = array(8); - if ($first_event_hour) { - $early_hours[] = $first_event_hour->format('G'); - } - if ($hour->format('G') < min($early_hours)) { - continue; - } - - $drawn_hourly_events = array(); - $cell_time = phutil_tag( - 'td', - array('class' => 'phui-calendar-day-hour'), - $hour->format('g A')); - - foreach ($hourly_events as $hourly_event) { - if ($hourly_event['hour'] == $hour) { - - $drawn_hourly_events[] = $this->drawEvent( - $hourly_event['event'], - $hourly_event['offset'], - $hourly_event['width'], - $hourly_event['top'], - $hourly_event['height']); - } - } - $cell_event = phutil_tag( - 'td', - array('class' => 'phui-calendar-day-events'), - $drawn_hourly_events); - - $row = phutil_tag( - 'tr', - array(), - array($cell_time, $cell_event)); - - $rows[] = $row; - } - - $table = phutil_tag( - 'table', - array('class' => 'phui-calendar-day-view'), - $rows); - - $all_day_event_box = new PHUIBoxView(); - foreach ($today_all_day_events as $all_day_event) { - $all_day_event_box->appendChild( - $this->drawAllDayEvent($all_day_event)); - } - $header = $this->renderDayViewHeader(); $sidebar = $this->renderSidebar(); $warnings = $this->getQueryRangeWarning(); + $table_id = celerity_generate_unique_node_id(); + + $table_wrapper = phutil_tag( + 'div', + array( + 'id' => $table_id, + ), + ''); + Javelin::initBehavior( 'day-view', array( + 'allDayEvents' => $js_today_all_day_events, 'todayEvents' => $this->jsTodayEvents, 'hourlyEvents' => $js_hourly_events, 'hours' => $js_hours, 'firstEventHour' => $first_event_hour->format('G'), + 'tableID' => $table_id, )); $table_box = id(new PHUIObjectBoxView()) ->setHeader($header) - ->appendChild($all_day_event_box) - ->appendChild($table) + ->appendChild($table_wrapper) ->setFormErrors($warnings) ->setFlush(true); @@ -433,75 +377,6 @@ final class PHUICalendarDayView extends AphrontView { return $hourly_events; } - private function drawAllDayEvent(AphrontCalendarEventView $event) { - $class = 'day-view-all-day'; - if ($event->getViewerIsInvited()) { - $class = $class.' viewer-invited-day-event'; - } - - $name = phutil_tag( - 'a', - array( - 'class' => $class, - 'href' => $event->getURI(), - ), - $event->getName()); - - $all_day_label = phutil_tag( - 'span', - array( - 'class' => 'phui-calendar-all-day-label', - ), - pht('All Day')); - - $div = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-day-event', - ), - array( - $all_day_label, - $name, - )); - - return $div; - } - - private function drawEvent( - AphrontCalendarEventView $event, - $offset, - $width, - $top, - $height) { - - $class = 'phui-calendar-day-event-link'; - if ($event->getViewerIsInvited()) { - $class = $class.' viewer-invited-day-event'; - } - - $name = phutil_tag( - 'a', - array( - 'class' => $class, - 'href' => $event->getURI(), - ), - $event->getName()); - - $div = phutil_tag( - 'div', - array( - 'class' => 'phui-calendar-day-event', - 'style' => 'left: '.$offset - .'; width: '.$width - .'; top: '.$top - .'; height: '.$height - .';', - ), - $name); - - return $div; - } - // returns DateTime of each hour in the day private function getHoursOfDay() { $included_datetimes = array(); diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index f37714c04f..9c42b7f719 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -8,6 +8,8 @@ JX.behavior('day-view', function(config) { var first_event_hour = config.firstEventHour; var hourly_events = config.hourlyEvents; var today_events = config.todayEvents; + var today_all_day_events = config.allDayEvents; + var table_wrapper = JX.$(config.tableID); function findTodayClusters() { @@ -80,19 +82,17 @@ JX.behavior('day-view', function(config) { return hourly_events; } - function drawEvent( - start, - end, - name, - viewerIsInvited, - uri, - id, - offset, - width, - top, - height) { + function drawEvent(hourly_event) { + var name = hourly_event['eventName']; + var viewerIsInvited = hourly_event['viewerIsInvited']; + var offset = hourly_event['offset']; + var width = hourly_event['width']; + var top = hourly_event['top']; + var height = hourly_event['height']; + var uri = hourly_events['uri']; var link_class = 'phui-calendar-day-event-link'; + if (viewerIsInvited) { link_class = link_class + ' viewer-invited-day-event'; } @@ -121,6 +121,36 @@ JX.behavior('day-view', function(config) { return div; } + function drawAllDayEvent( + viewerIsInvited, + uri, + name) { + var class_name = 'day-view-all-day'; + if (viewerIsInvited) { + class_name = class_name + ' viewer-invited-day-event'; + } + + name = JX.$N( + 'a', + { + className: class_name, + href: uri + }, + name); + + var all_day_label = JX.$N( + 'span', + {className: 'phui-calendar-all-day-label'}, + 'All Day'); + + var div_all_day = JX.$N( + 'div', + {className: 'phui-calendar-day-event'}, + [all_day_label, name]); + + return div_all_day; + } + function drawRows() { var rows = []; var early_hours = [8]; @@ -142,20 +172,7 @@ JX.behavior('day-view', function(config) { for (var j=0; j < hourly_events.length; j++) { if (hourly_events[j]['hour'] == hours[i]['hour']) { - drawn_hourly_events.push( - drawEvent( - hourly_events[j]['eventStartEpoch'], - hourly_events[j]['eventEndEpoch'], - hourly_events[j]['eventName'], - hourly_events[j]['eventID'], - hourly_events[j]['viewerIsInvited'], - hourly_events[j]['hour'], - hourly_events[j]['offset'], - hourly_events[j]['width'], - hourly_events[j]['top'], - hourly_events[j]['height'] - ) - ); + drawn_hourly_events.push(drawEvent(hourly_events[j])); } } @@ -179,4 +196,20 @@ JX.behavior('day-view', function(config) { hourly_events = updateEventsFromCluster(today_clusters[i], hourly_events); } var rows = drawRows(); + + var all_day_events = []; + for(i=0; i < today_all_day_events.length; i++) { + var all_day_event = today_all_day_events[i]; + all_day_events.push(drawAllDayEvent( + all_day_event['viewerIsInvited'], + all_day_event['uri'], + all_day_event['name'])); + } + + var table = JX.$N( + 'table', + {className: 'phui-calendar-day-view'}, + rows); + + JX.DOM.setContent(table_wrapper, [all_day_events, table]); }); From 7d757483a0db4384117ab4c412a0db4065696103 Mon Sep 17 00:00:00 2001 From: epriestley Date: Sat, 23 May 2015 05:36:02 -0700 Subject: [PATCH 06/43] Make everything 1000x or 1000000x slower Summary: In the great `pht()` conversion, some strings like "123,456" are now being printed as numbers with "%d". These come out as "123" instead of "123,456". Use "%s" and "PhutilNumber" to present numbers with comma groupings. Test Plan: - Viewed DarkConsole. - Viewed conduit logs. - Viewed daemon logs. - Grepped for `%d ms` and `%d us`. Reviewers: btrahan, joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D12979 --- .../controller/PhabricatorConduitLogController.php | 2 +- .../console/plugin/DarkConsoleServicesPlugin.php | 8 +++++--- .../controller/PhabricatorDaemonConsoleController.php | 2 +- .../controller/PhabricatorWorkerTaskDetailController.php | 2 +- .../herald/query/HeraldTranscriptSearchEngine.php | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/applications/conduit/controller/PhabricatorConduitLogController.php b/src/applications/conduit/controller/PhabricatorConduitLogController.php index 2effa1815b..62f3a24905 100644 --- a/src/applications/conduit/controller/PhabricatorConduitLogController.php +++ b/src/applications/conduit/controller/PhabricatorConduitLogController.php @@ -98,7 +98,7 @@ final class PhabricatorConduitLogController array($call->getMethod(), $client), $status, $call->getError(), - pht('%d us', number_format($call->getDuration())), + pht('%s us', new PhutilNumber($call->getDuration())), phabricator_datetime($call->getDateCreated(), $viewer), ); } diff --git a/src/applications/console/plugin/DarkConsoleServicesPlugin.php b/src/applications/console/plugin/DarkConsoleServicesPlugin.php index 6a59c7e129..055315d641 100644 --- a/src/applications/console/plugin/DarkConsoleServicesPlugin.php +++ b/src/applications/console/plugin/DarkConsoleServicesPlugin.php @@ -198,7 +198,7 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin { $summary[] = array( $type, number_format($counts[$type]), - pht('%d us', number_format((int)(1000000 * $totals[$type]))), + pht('%s us', new PhutilNumber((int)(1000000 * $totals[$type]))), sprintf('%.1f%%', 100 * $totals[$type] / $page_total), ); } @@ -258,10 +258,12 @@ final class DarkConsoleServicesPlugin extends DarkConsolePlugin { break; } + $offset = ($row['begin'] - $data['start']); + $rows[] = array( $row['type'], - pht('+%d ms', number_format(1000 * ($row['begin'] - $data['start']))), - pht('%d us', number_format(1000000 * $row['duration'])), + pht('+%s ms', new PhutilNumber(1000 * $offset)), + pht('%s us', new PhutilNumber(1000000 * $row['duration'])), $info, $analysis, ); diff --git a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php index 76d98bda5c..921921942c 100644 --- a/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php +++ b/src/applications/daemon/controller/PhabricatorDaemonConsoleController.php @@ -50,7 +50,7 @@ final class PhabricatorDaemonConsoleController $rows[] = array( $class, number_format($info['n']), - pht('%d us', number_format((int)($info['duration'] / $info['n']))), + pht('%s us', new PhutilNumber((int)($info['duration'] / $info['n']))), ); } diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php index 57951ccb91..66925ffd36 100644 --- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php +++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php @@ -141,7 +141,7 @@ final class PhabricatorWorkerTaskDetailController $expires); if ($task->isArchived()) { - $duration = pht('%d us', number_format($task->getDuration())); + $duration = pht('%s us', new PhutilNumber($task->getDuration())); } else { $duration = phutil_tag('em', array(), pht('Not Completed')); } diff --git a/src/applications/herald/query/HeraldTranscriptSearchEngine.php b/src/applications/herald/query/HeraldTranscriptSearchEngine.php index 1fd01c3e0d..f9433e0c02 100644 --- a/src/applications/herald/query/HeraldTranscriptSearchEngine.php +++ b/src/applications/herald/query/HeraldTranscriptSearchEngine.php @@ -125,7 +125,7 @@ final class HeraldTranscriptSearchEngine } $item->addAttribute($handles[$xscript->getObjectPHID()]->renderLink()); $item->addAttribute( - pht('%d ms', number_format((int)(1000 * $xscript->getDuration())))); + pht('%s ms', new PhutilNumber((int)(1000 * $xscript->getDuration())))); $item->addIcon( 'none', phabricator_datetime($xscript->getTime(), $viewer)); From 963485a3da25c97d0ba480f7179f6fe51ea153ee Mon Sep 17 00:00:00 2001 From: lkassianik Date: Sat, 23 May 2015 19:47:23 -0700 Subject: [PATCH 07/43] Rescheduling events by dragging them in day view Summary: Ref T8300, Rescheduling events by dragging them in day view Test Plan: Open day view, drag events, observe them reschedule. Reviewers: chad, epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8300 Differential Revision: https://secure.phabricator.com/D12988 --- resources/celerity/map.php | 14 +- src/__phutil_library_map__.php | 2 + .../PhabricatorCalendarApplication.php | 2 + ...PhabricatorCalendarEventDragController.php | 66 +++++++ .../phui/calendar/PHUICalendarDayView.php | 81 ++++---- webroot/rsrc/css/core/z-index.css | 4 + .../css/phui/calendar/phui-calendar-day.css | 12 +- .../application/calendar/behavior-day-view.js | 183 ++++++++++++++---- 8 files changed, 274 insertions(+), 90 deletions(-) create mode 100644 src/applications/calendar/controller/PhabricatorCalendarEventDragController.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 422fee0b9d..1a237ab2d5 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '36142bff', + 'core.pkg.css' => '439658b5', 'core.pkg.js' => '328799d0', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', @@ -112,7 +112,7 @@ return array( 'rsrc/css/core/core.css' => 'aaea7a7a', 'rsrc/css/core/remarkup.css' => '07b7dc54', 'rsrc/css/core/syntax.css' => '6b7b24d9', - 'rsrc/css/core/z-index.css' => '8414a09b', + 'rsrc/css/core/z-index.css' => 'c4732d32', 'rsrc/css/diviner/diviner-shared.css' => '38813222', 'rsrc/css/font/font-awesome.css' => 'e2e712fe', 'rsrc/css/font/font-source-sans-pro.css' => '8906c07b', @@ -121,7 +121,7 @@ return array( 'rsrc/css/layout/phabricator-hovercard-view.css' => 'dd9121a9', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'c1db9e9c', 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'c0cf782a', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'feba82c5', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', @@ -331,7 +331,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/behavior-day-view.js' => 'f4f4ad80', + 'rsrc/js/application/calendar/behavior-day-view.js' => 'dc0065ab', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', @@ -554,7 +554,7 @@ return array( 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', - 'javelin-behavior-day-view' => 'f4f4ad80', + 'javelin-behavior-day-view' => 'dc0065ab', 'javelin-behavior-device' => 'a205cf28', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', @@ -752,7 +752,7 @@ return array( 'phabricator-uiexample-reactor-select' => 'a155550f', 'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', - 'phabricator-zindex-css' => '8414a09b', + 'phabricator-zindex-css' => 'c4732d32', 'phame-css' => '88bd4705', 'pholio-css' => '95174bdd', 'pholio-edit-css' => '3ad9d1ee', @@ -767,7 +767,7 @@ return array( 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'de610129', 'phui-calendar-css' => 'ccabe893', - 'phui-calendar-day-css' => 'c0cf782a', + 'phui-calendar-day-css' => 'feba82c5', 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => '594d719e', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index cbc2e27de1..bb4a613cae 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1498,6 +1498,7 @@ phutil_register_library_map(array( 'PhabricatorCalendarEvent' => 'applications/calendar/storage/PhabricatorCalendarEvent.php', 'PhabricatorCalendarEventCancelController' => 'applications/calendar/controller/PhabricatorCalendarEventCancelController.php', 'PhabricatorCalendarEventCommentController' => 'applications/calendar/controller/PhabricatorCalendarEventCommentController.php', + 'PhabricatorCalendarEventDragController' => 'applications/calendar/controller/PhabricatorCalendarEventDragController.php', 'PhabricatorCalendarEventEditController' => 'applications/calendar/controller/PhabricatorCalendarEventEditController.php', 'PhabricatorCalendarEventEditIconController' => 'applications/calendar/controller/PhabricatorCalendarEventEditIconController.php', 'PhabricatorCalendarEventEditor' => 'applications/calendar/editor/PhabricatorCalendarEventEditor.php', @@ -4853,6 +4854,7 @@ phutil_register_library_map(array( ), 'PhabricatorCalendarEventCancelController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventCommentController' => 'PhabricatorCalendarController', + 'PhabricatorCalendarEventDragController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditIconController' => 'PhabricatorCalendarController', 'PhabricatorCalendarEventEditor' => 'PhabricatorApplicationTransactionEditor', diff --git a/src/applications/calendar/application/PhabricatorCalendarApplication.php b/src/applications/calendar/application/PhabricatorCalendarApplication.php index 534b11d2e8..d41577a3af 100644 --- a/src/applications/calendar/application/PhabricatorCalendarApplication.php +++ b/src/applications/calendar/application/PhabricatorCalendarApplication.php @@ -54,6 +54,8 @@ final class PhabricatorCalendarApplication extends PhabricatorApplication { => 'PhabricatorCalendarEventEditController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventEditController', + 'drag/(?P[1-9]\d*)/' + => 'PhabricatorCalendarEventDragController', 'cancel/(?P[1-9]\d*)/' => 'PhabricatorCalendarEventCancelController', '(?Pjoin|decline|accept)/(?P[1-9]\d*)/' diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php new file mode 100644 index 0000000000..7b813cdc44 --- /dev/null +++ b/src/applications/calendar/controller/PhabricatorCalendarEventDragController.php @@ -0,0 +1,66 @@ +getViewer(); + + $event = id(new PhabricatorCalendarEventQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$event) { + return new Aphront404Response(); + } + + if (!$request->validateCSRF()) { + return new Aphront400Response(); + } + + if ($event->getIsAllDay()) { + return new Aphront400Response(); + } + + $xactions = array(); + + $duration = $event->getDateTo() - $event->getDateFrom(); + + $start = $request->getInt('start'); + $start_value = id(AphrontFormDateControlValue::newFromEpoch( + $viewer, + $start)); + + $end = $start + $duration; + $end_value = id(AphrontFormDateControlValue::newFromEpoch( + $viewer, + $end)); + + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_START_DATE) + ->setNewValue($start_value); + + $xactions[] = id(new PhabricatorCalendarEventTransaction()) + ->setTransactionType( + PhabricatorCalendarEventTransaction::TYPE_END_DATE) + ->setNewValue($end_value); + + + $editor = id(new PhabricatorCalendarEventEditor()) + ->setActor($viewer) + ->setContinueOnMissingFields(true) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + $xactions = $editor->applyTransactions($event, $xactions); + + return id(new AphrontReloadResponse()); + } +} diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 30d35e93fd..9901ad81b0 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -9,7 +9,6 @@ final class PHUICalendarDayView extends AphrontView { private $year; private $browseURI; private $events = array(); - private $jsTodayEvents = array(); private $allDayEvents = array(); @@ -44,8 +43,11 @@ final class PHUICalendarDayView extends AphrontView { public function render() { require_celerity_resource('phui-calendar-day-css'); + $viewer = $this->getUser(); + $hours = $this->getHoursOfDay(); $js_hours = array(); + $js_today_events = array(); foreach ($hours as $hour) { $js_hours[] = array( @@ -55,10 +57,7 @@ final class PHUICalendarDayView extends AphrontView { } $first_event_hour = null; - - $js_hourly_events = array(); $js_today_all_day_events = array(); - $all_day_events = $this->getAllDayEvents(); $day_start = $this->getDateTime(); @@ -81,60 +80,54 @@ final class PHUICalendarDayView extends AphrontView { } } - foreach ($hours as $hour) { - $current_hour_events = array(); - $hour_start = $hour->format('U'); - $hour_end = id(clone $hour)->modify('+1 hour')->format('U'); + $this->events = msort($this->events, 'getEpochStart'); - foreach ($this->events as $event) { - if ($event->getIsAllDay()) { - continue; - } - if (($hour == $day_start && - $event->getEpochStart() <= $hour_start && - $event->getEpochEnd() > $day_start_epoch) || - ($event->getEpochStart() >= $hour_start - && $event->getEpochStart() < $hour_end)) { - $current_hour_events[] = $event; - $this->jsTodayEvents[] = array( - 'eventStartEpoch' => $event->getEpochStart(), - 'eventEndEpoch' => $event->getEpochEnd(), - 'eventName' => $event->getName(), - 'eventID' => $event->getEventID(), - 'viewerIsInvited' => $event->getViewerIsInvited(), - 'uri' => $event->getURI(), - ); - } + if (!$this->events) { + $first_event_hour = $this->getDateTime()->setTime(8, 0, 0); + } + + foreach ($this->events as $event) { + if ($event->getIsAllDay()) { + continue; } - foreach ($current_hour_events as $event) { - $day_start_epoch = $this->getDateTime()->format('U'); + if ($event->getEpochStart() <= $day_end_epoch && + $event->getEpochEnd() > $day_start_epoch) { + + if ($first_event_hour === null) { + $first_event_hour = new DateTime('@'.$event->getEpochStart()); + $first_event_hour->setTimeZone($viewer->getTimeZone()); + $eight_am = $this->getDateTime()->setTime(8, 0, 0); + if ($eight_am->format('U') < $first_event_hour->format('U')) { + $first_event_hour = clone $eight_am; + } + } + $event_start = max($event->getEpochStart(), $day_start_epoch); $event_end = min($event->getEpochEnd(), $day_end_epoch); - $top = (($event_start - $hour_start) / ($hour_end - $hour_start)) - * 100; + $day_duration = ($day_end_epoch - $first_event_hour->format('U')) / 60; + + $top = (($event_start - $first_event_hour->format('U')) + / ($day_end_epoch - $first_event_hour->format('U'))) + * $day_duration; $top = max(0, $top); - $height = (($event_end - $event_start) / ($hour_end - $hour_start)) - * 100; - $height = min(2400, $height); + $height = (($event_end - $event_start) + / ($day_end_epoch - $first_event_hour->format('U'))) + * $day_duration; + $height = min($day_duration, $height); - if ($first_event_hour === null) { - $first_event_hour = $hour; - } - - $js_hourly_events[] = array( + $js_today_events[] = array( 'eventStartEpoch' => $event->getEpochStart(), 'eventEndEpoch' => $event->getEpochEnd(), 'eventName' => $event->getName(), 'eventID' => $event->getEventID(), 'viewerIsInvited' => $event->getViewerIsInvited(), 'uri' => $event->getURI(), - 'hour' => $hour->format('G'), 'offset' => '0', 'width' => '100%', - 'top' => $top.'%', - 'height' => $height.'%', + 'top' => $top.'px', + 'height' => $height.'px', ); } } @@ -156,10 +149,10 @@ final class PHUICalendarDayView extends AphrontView { 'day-view', array( 'allDayEvents' => $js_today_all_day_events, - 'todayEvents' => $this->jsTodayEvents, - 'hourlyEvents' => $js_hourly_events, + 'todayEvents' => $js_today_events, 'hours' => $js_hours, 'firstEventHour' => $first_event_hour->format('G'), + 'firstEventHourEpoch' => $first_event_hour->format('U'), 'tableID' => $table_id, )); diff --git a/webroot/rsrc/css/core/z-index.css b/webroot/rsrc/css/core/z-index.css index 11cb76de27..e22187dcda 100644 --- a/webroot/rsrc/css/core/z-index.css +++ b/webroot/rsrc/css/core/z-index.css @@ -36,6 +36,10 @@ z-index: 2; } +div.phui-calendar-day-event { + z-index: 2; +} + .slowvote-above-the-bar { z-index: 3; } diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index 715bd6fd3b..e90e2b12aa 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -35,7 +35,11 @@ border-top: 1px solid {$lightgreyborder}; } -.phui-calendar-day-view td div.phui-calendar-day-event { +.phui-drag { + opacity: .25; +} + +div.phui-calendar-day-event { width: 100%; position: absolute; top: 0; @@ -43,11 +47,15 @@ min-height: 30px; } +div.phui-calendar-day-event.all-day { + position: relative; +} + .phui-calendar-day-event-link { padding: 8px; border: 1px solid {$greyborder}; background-color: {$darkgreybackground}; - margin: 0 4px; + margin: 0 1px; position: absolute; left: 0; right: 0; diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index 9c42b7f719..ae4aaf6b6b 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -4,13 +4,6 @@ JX.behavior('day-view', function(config) { - var hours = config.hours; - var first_event_hour = config.firstEventHour; - var hourly_events = config.hourlyEvents; - var today_events = config.todayEvents; - var today_all_day_events = config.allDayEvents; - var table_wrapper = JX.$(config.tableID); - function findTodayClusters() { var events = today_events.sort(function(x, y){ @@ -23,8 +16,8 @@ JX.behavior('day-view', function(config) { var today_event = events[i]; var destination_cluster_index = null; - var event_start = today_event.eventStartEpoch - (30*60); - var event_end = today_event.eventEndEpoch + (30*60); + var event_start = today_event.eventStartEpoch - (60); + var event_end = today_event.eventEndEpoch + (60); for (var j=0; j < clusters.length; j++) { var cluster = clusters[j]; @@ -59,7 +52,7 @@ JX.behavior('day-view', function(config) { return clusters; } - function updateEventsFromCluster(cluster, hourly_events) { + function updateEventsFromCluster(cluster) { var cluster_size = cluster.length; var n = 0; for(var i=0; i < cluster.length; i++) { @@ -69,28 +62,30 @@ JX.behavior('day-view', function(config) { var offset = ((n / cluster_size) * 100) + '%'; var width = ((1 / cluster_size) * 100) + '%'; - for (var j=0; j < hourly_events.length; j++) { - if (hourly_events[j].eventID == event_id) { + for (var j=0; j < today_events.length; j++) { + if (today_events[j].eventID == event_id) { - hourly_events[j]['offset'] = offset; - hourly_events[j]['width'] = width; + today_events[j]['offset'] = offset; + today_events[j]['width'] = width; } } n++; } - return hourly_events; + return today_events; } - function drawEvent(hourly_event) { - var name = hourly_event['eventName']; - var viewerIsInvited = hourly_event['viewerIsInvited']; - var offset = hourly_event['offset']; - var width = hourly_event['width']; - var top = hourly_event['top']; - var height = hourly_event['height']; - var uri = hourly_events['uri']; + function drawEvent(e) { + var name = e['eventName']; + var eventID = e['eventID']; + var viewerIsInvited = e['viewerIsInvited']; + var offset = e['offset']; + var width = e['width']; + var top = e['top']; + var height = e['height']; + var uri = e['uri']; + var sigil = 'phui-calendar-day-event'; var link_class = 'phui-calendar-day-event-link'; if (viewerIsInvited) { @@ -109,6 +104,8 @@ JX.behavior('day-view', function(config) { 'div', { className: 'phui-calendar-day-event', + sigil: sigil, + meta: {eventID: eventID, record: e, uri: uri}, style: { left: offset, width: width, @@ -145,7 +142,7 @@ JX.behavior('day-view', function(config) { var div_all_day = JX.$N( 'div', - {className: 'phui-calendar-day-event'}, + {className: 'phui-calendar-day-event all-day'}, [all_day_label, name]); return div_all_day; @@ -164,24 +161,17 @@ JX.behavior('day-view', function(config) { if (hours[i]['hour'] < min_early_hour) { continue; } - var drawn_hourly_events = []; var cell_time = JX.$N( 'td', {className: 'phui-calendar-day-hour'}, hours[i]['hour_meridian']); - for (var j=0; j < hourly_events.length; j++) { - if (hourly_events[j]['hour'] == hours[i]['hour']) { - drawn_hourly_events.push(drawEvent(hourly_events[j])); - } - } - var cell_event = JX.$N( 'td', { className: 'phui-calendar-day-events' - }, - drawn_hourly_events); + }); + var row = JX.$N( 'tr', {}, @@ -191,10 +181,26 @@ JX.behavior('day-view', function(config) { return rows; } - var today_clusters = findTodayClusters(); - for(var i=0; i < today_clusters.length; i++) { - hourly_events = updateEventsFromCluster(today_clusters[i], hourly_events); + function clusterAndDrawEvents() { + var today_clusters = findTodayClusters(); + for(var i=0; i < today_clusters.length; i++) { + today_events = updateEventsFromCluster(today_clusters[i]); + } + var drawn_hourly_events = []; + for (i=0; i < today_events.length; i++) { + drawn_hourly_events.push(drawEvent(today_events[i])); + } + + JX.DOM.setContent(hourly_events_wrapper, drawn_hourly_events); + } + + var hours = config.hours; + var first_event_hour = config.firstEventHour; + var first_event_hour_epoch = parseInt(config.firstEventHourEpoch, 10); + var today_events = config.todayEvents; + var today_all_day_events = config.allDayEvents; + var table_wrapper = JX.$(config.tableID); var rows = drawRows(); var all_day_events = []; @@ -211,5 +217,108 @@ JX.behavior('day-view', function(config) { {className: 'phui-calendar-day-view'}, rows); - JX.DOM.setContent(table_wrapper, [all_day_events, table]); + var dragging = false; + var origin = null; + + var offset_top = null; + var new_top = null; + + var click_time = null; + + JX.DOM.listen( + table_wrapper, + 'mousedown', + 'phui-calendar-day-event', + function(e){ + + if (!e.isNormalMouseEvent()) { + return; + } + e.kill(); + dragging = e.getNode('phui-calendar-day-event'); + JX.DOM.alterClass(dragging, 'phui-drag', true); + + click_time = new Date(); + + origin = JX.$V(e); + + var outer = JX.Vector.getPos(table); + var inner = JX.Vector.getPos(dragging); + + offset_top = inner.y - outer.y; + new_top = offset_top; + + dragging.style.top = offset_top + 'px'; + }); + JX.Stratcom.listen('mousemove', null, function(e){ + if (!dragging) { + return; + } + var cursor = JX.$V(e); + + new_top = cursor.y - origin.y + offset_top; + new_top = Math.min(new_top, 1320); + new_top = Math.max(new_top, 0); + new_top = Math.floor(new_top/15) * 15; + + dragging.style.top = new_top + 'px'; + }); + JX.Stratcom.listen('mouseup', null, function(){ + var data = JX.Stratcom.getData(dragging); + var record = data.record; + + if (!dragging) { + return; + } + if (new_top == offset_top) { + var now = new Date(); + if (now.getTime() - click_time.getTime() < 250) { + JX.$U(record.uri).go(); + } + + JX.DOM.alterClass(dragging, 'phui-drag', false); + dragging = false; + return; + } + var new_time = first_event_hour_epoch + (new_top * 60); + var id = data.eventID; + var duration = record.eventEndEpoch - record.eventStartEpoch; + record.eventStartEpoch = new_time; + record.eventEndEpoch = new_time + duration; + record.top = new_top + 'px'; + + new JX.Workflow( + '/calendar/event/drag/' + id + '/', + {start: new_time}) + .start(); + + JX.DOM.alterClass(dragging, 'phui-drag', false); + dragging = false; + + clusterAndDrawEvents(); + }); + + JX.DOM.listen(table_wrapper, 'click', 'phui-calendar-day-event', function(e){ + if (e.isNormalClick()) { + e.kill(); + } + }); + + var hourly_events_wrapper = JX.$N( + 'div', + {style: { + position: 'absolute', + left: '69px', + right: 0 + }}); + + clusterAndDrawEvents(); + + var daily_wrapper = JX.$N( + 'div', + {style: {position: 'relative'}}, + [hourly_events_wrapper, table]); + + JX.DOM.setContent(table_wrapper, [all_day_events, daily_wrapper]); + }); From 18fe6d58aeba41c6ac17e41c6f374ea429668e6a Mon Sep 17 00:00:00 2001 From: lkassianik Date: Sun, 24 May 2015 11:22:33 -0700 Subject: [PATCH 08/43] Clicking in day view should create new event Summary: Ref T8300, clicking in day view should create new event Test Plan: Open day view, click in an empty slot, new event modal should open. Reviewers: epriestley, #blessed_reviewers, chad Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8300 Differential Revision: https://secure.phabricator.com/D12990 --- resources/celerity/map.php | 8 +- ...PhabricatorCalendarEventEditController.php | 95 ++++++++++++++----- .../PhabricatorCalendarEventSearchEngine.php | 11 ++- .../view/AphrontCalendarEventView.php | 9 ++ .../control/AphrontFormDateControlValue.php | 13 +++ .../phui/calendar/PHUICalendarDayView.php | 14 +++ .../css/phui/calendar/phui-calendar-day.css | 9 ++ .../application/calendar/behavior-day-view.js | 48 +++++++++- 8 files changed, 173 insertions(+), 34 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 1a237ab2d5..b32f74bbad 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -121,7 +121,7 @@ return array( 'rsrc/css/layout/phabricator-hovercard-view.css' => 'dd9121a9', 'rsrc/css/layout/phabricator-side-menu-view.css' => 'c1db9e9c', 'rsrc/css/layout/phabricator-source-code-view.css' => '2ceee894', - 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'feba82c5', + 'rsrc/css/phui/calendar/phui-calendar-day.css' => 'd1cf6f93', 'rsrc/css/phui/calendar/phui-calendar-list.css' => 'c1c7f338', 'rsrc/css/phui/calendar/phui-calendar-month.css' => '476be7e0', 'rsrc/css/phui/calendar/phui-calendar.css' => 'ccabe893', @@ -331,7 +331,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/behavior-day-view.js' => 'dc0065ab', + 'rsrc/js/application/calendar/behavior-day-view.js' => '28a60488', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', @@ -554,7 +554,7 @@ return array( 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', - 'javelin-behavior-day-view' => 'dc0065ab', + 'javelin-behavior-day-view' => '28a60488', 'javelin-behavior-device' => 'a205cf28', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', @@ -767,7 +767,7 @@ return array( 'phui-box-css' => '7b3a2eed', 'phui-button-css' => 'de610129', 'phui-calendar-css' => 'ccabe893', - 'phui-calendar-day-css' => 'feba82c5', + 'phui-calendar-day-css' => 'd1cf6f93', 'phui-calendar-list-css' => 'c1c7f338', 'phui-calendar-month-css' => '476be7e0', 'phui-crumbs-view-css' => '594d719e', diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index 41744edecf..cc19f7273f 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -14,8 +14,8 @@ final class PhabricatorCalendarEventEditController } public function handleRequest(AphrontRequest $request) { - $user = $request->getUser(); - $user_phid = $user->getPHID(); + $viewer = $request->getViewer(); + $user_phid = $viewer->getPHID(); $error_name = true; $error_start_date = true; $error_end_date = true; @@ -25,9 +25,41 @@ final class PhabricatorCalendarEventEditController $start_date_id = celerity_generate_unique_node_id(); $end_date_id = null; + $next_workflow = $request->getStr('next'); + $uri_query = $request->getStr('query'); + if ($this->isCreate()) { - $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($user); - list($start_value, $end_value) = $this->getDefaultTimeValues($user); + $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($viewer); + + $create_start_year = $request->getInt('year'); + $create_start_month = $request->getInt('month'); + $create_start_day = $request->getInt('day'); + $create_start_time = $request->getStr('time'); + + if ($create_start_year) { + $start = AphrontFormDateControlValue::newFromParts( + $viewer, + $create_start_year, + $create_start_month, + $create_start_day, + $create_start_time); + if (!$start->isValid()) { + return new Aphront400Response(); + } + $start_value = AphrontFormDateControlValue::newFromEpoch( + $viewer, + $start->getEpoch()); + + $end = clone $start_value->getDateTime(); + $end->modify('+1 hour'); + $end_value = AphrontFormDateControlValue::newFromEpoch( + $viewer, + $end->format('U')); + + } else { + list($start_value, $end_value) = $this->getDefaultTimeValues($viewer); + } + $submit_label = pht('Create'); $page_title = pht('Create Event'); @@ -38,7 +70,7 @@ final class PhabricatorCalendarEventEditController $end_date_id = celerity_generate_unique_node_id(); } else { $event = id(new PhabricatorCalendarEventQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withIDs(array($this->id)) ->requireCapabilities( array( @@ -51,10 +83,10 @@ final class PhabricatorCalendarEventEditController } $end_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $event->getDateTo()); $start_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $event->getDateFrom()); $submit_label = pht('Update'); @@ -81,7 +113,7 @@ final class PhabricatorCalendarEventEditController $icon = $event->getIcon(); $current_policies = id(new PhabricatorPolicyQuery()) - ->setViewer($user) + ->setViewer($viewer) ->setObject($event) ->execute(); @@ -106,9 +138,9 @@ final class PhabricatorCalendarEventEditController $new_invitees = $this->getNewInviteeList($invitees, $event); $status_attending = PhabricatorCalendarEventInvitee::STATUS_ATTENDING; if ($this->isCreate()) { - $status = idx($new_invitees, $user->getPHID()); + $status = idx($new_invitees, $viewer->getPHID()); if ($status) { - $new_invitees[$user->getPHID()] = $status_attending; + $new_invitees[$viewer->getPHID()] = $status_attending; } } @@ -161,14 +193,29 @@ final class PhabricatorCalendarEventEditController ->setNewValue($request->getStr('editPolicy')); $editor = id(new PhabricatorCalendarEventEditor()) - ->setActor($user) + ->setActor($viewer) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true); try { $xactions = $editor->applyTransactions($event, $xactions); $response = id(new AphrontRedirectResponse()); - return $response->setURI('/E'.$event->getID()); + switch ($next_workflow) { + case 'day': + if (!$uri_query) { + $uri_query = 'month'; + } + $year = $start_value->getDateTime()->format('Y'); + $month = $start_value->getDateTime()->format('m'); + $day = $start_value->getDateTime()->format('d'); + $response->setURI( + '/calendar/query/'.$uri_query.'/'.$year.'/'.$month.'/'.$day.'/'); + break; + default: + $response->setURI('/E'.$event->getID()); + break; + } + return $response; } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; $error_name = $ex->getShortMessage( @@ -204,7 +251,7 @@ final class PhabricatorCalendarEventEditController $all_day_id); $start_control = id(new AphrontFormDateControl()) - ->setUser($user) + ->setUser($viewer) ->setName('start') ->setLabel(pht('Start')) ->setError($error_start_date) @@ -214,7 +261,7 @@ final class PhabricatorCalendarEventEditController ->setEndDateID($end_date_id); $end_control = id(new AphrontFormDateControl()) - ->setUser($user) + ->setUser($viewer) ->setName('end') ->setLabel(pht('End')) ->setError($error_end_date) @@ -228,13 +275,13 @@ final class PhabricatorCalendarEventEditController ->setValue($description); $view_policies = id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($event) ->setPolicies($current_policies) ->setName('viewPolicy'); $edit_policies = id(new AphrontFormPolicyControl()) - ->setUser($user) + ->setUser($viewer) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($event) ->setPolicies($current_policies) @@ -244,14 +291,14 @@ final class PhabricatorCalendarEventEditController ->setLabel(pht('Subscribers')) ->setName('subscribers') ->setValue($subscribers) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); $invitees = id(new AphrontFormTokenizerControl()) ->setLabel(pht('Invitees')) ->setName('invitees') ->setValue($invitees) - ->setUser($user) + ->setUser($viewer) ->setDatasource(new PhabricatorMetaMTAMailableDatasource()); if ($this->isCreate()) { @@ -269,7 +316,9 @@ final class PhabricatorCalendarEventEditController ->setValue($icon); $form = id(new AphrontFormView()) - ->setUser($user) + ->addHiddenInput('next', $next_workflow) + ->addHiddenInput('query', $uri_query) + ->setUser($viewer) ->appendChild($name) ->appendChild($all_day_checkbox) ->appendChild($start_control) @@ -351,19 +400,19 @@ final class PhabricatorCalendarEventEditController return $new; } - private function getDefaultTimeValues($user) { + private function getDefaultTimeValues($viewer) { $start = new DateTime('@'.time()); - $start->setTimeZone($user->getTimeZone()); + $start->setTimeZone($viewer->getTimeZone()); $start->setTime($start->format('H'), 0, 0); $start->modify('+1 hour'); $end = id(clone $start)->modify('+1 hour'); $start_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $start->format('U')); $end_value = AphrontFormDateControlValue::newFromEpoch( - $user, + $viewer, $end->format('U')); return array($start_value, $end_value); diff --git a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php index b9ca2b81bf..b42b5dcdf8 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventSearchEngine.php @@ -390,12 +390,13 @@ final class PhabricatorCalendarEventSearchEngine list($start_year, $start_month, $start_day) = $this->getDisplayYearAndMonthAndDay($query); - $day_view = new PHUICalendarDayView( + $day_view = id(new PHUICalendarDayView( $this->getDateFrom($query), $this->getDateTo($query), $start_year, $start_month, - $start_day); + $start_day)) + ->setQuery($query->getQueryKey()); $day_view->setUser($viewer); @@ -408,7 +409,13 @@ final class PhabricatorCalendarEventSearchEngine $viewer_is_invited = $status->getIsUserInvited($viewer->getPHID()); + $can_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $status, + PhabricatorPolicyCapability::CAN_EDIT); + $event = new AphrontCalendarEventView(); + $event->setCanEdit($can_edit); $event->setEventID($status->getID()); $event->setEpochRange($status->getDateFrom(), $status->getDateTo()); $event->setIsAllDay($status->getIsAllDay()); diff --git a/src/applications/calendar/view/AphrontCalendarEventView.php b/src/applications/calendar/view/AphrontCalendarEventView.php index fadf5eb889..5bc0b71e31 100644 --- a/src/applications/calendar/view/AphrontCalendarEventView.php +++ b/src/applications/calendar/view/AphrontCalendarEventView.php @@ -12,6 +12,7 @@ final class AphrontCalendarEventView extends AphrontView { private $uri; private $isAllDay; private $icon; + private $canEdit; public function setURI($uri) { $this->uri = $uri; @@ -97,6 +98,14 @@ final class AphrontCalendarEventView extends AphrontView { return $this->icon; } + public function setCanEdit($can_edit) { + $this->canEdit = $can_edit; + return $this; + } + + public function getCanEdit() { + return $this->canEdit; + } public function getMultiDay() { $nextday = strtotime('12:00 AM Tomorrow', $this->getEpochStart()); diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index 3fb6a07fb4..28d52371a9 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -211,6 +211,19 @@ final class AphrontFormDateControlValue extends Phobject { return $value; } + public function getDateTime() { + $epoch = $this->getEpoch(); + $date = null; + + if ($epoch) { + $zone = $this->getTimezone(); + $date = new DateTime('@'.$epoch); + $date->setTimeZone($zone); + } + + return $date; + } + private function getTimezone() { if ($this->zone) { return $this->zone; diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 9901ad81b0..2187d08341 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -8,6 +8,7 @@ final class PHUICalendarDayView extends AphrontView { private $month; private $year; private $browseURI; + private $query; private $events = array(); private $allDayEvents = array(); @@ -25,6 +26,14 @@ final class PHUICalendarDayView extends AphrontView { return $this->browseURI; } + public function setQuery($query) { + $this->query = $query; + return $this; + } + private function getQuery() { + return $this->query; + } + public function __construct( $range_start, $range_end, @@ -128,6 +137,7 @@ final class PHUICalendarDayView extends AphrontView { 'width' => '100%', 'top' => $top.'px', 'height' => $height.'px', + 'canEdit' => $event->getCanEdit(), ); } } @@ -148,6 +158,10 @@ final class PHUICalendarDayView extends AphrontView { Javelin::initBehavior( 'day-view', array( + 'year' => $first_event_hour->format('Y'), + 'month' => $first_event_hour->format('m'), + 'day' => $first_event_hour->format('d'), + 'query' => $this->getQuery(), 'allDayEvents' => $js_today_all_day_events, 'todayEvents' => $js_today_events, 'hours' => $js_hours, diff --git a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css index e90e2b12aa..e012be0afa 100644 --- a/webroot/rsrc/css/phui/calendar/phui-calendar-day.css +++ b/webroot/rsrc/css/phui/calendar/phui-calendar-day.css @@ -29,6 +29,11 @@ .phui-calendar-day-view td { position: relative; + cursor: pointer; +} + +.phui-calendar-day-view td:hover { + background: {$lightbluebackground}; } .phui-calendar-day-view tr + tr td.phui-calendar-day-events { @@ -47,6 +52,10 @@ div.phui-calendar-day-event { min-height: 30px; } +.can-drag a { + cursor: move; +} + div.phui-calendar-day-event.all-day { position: relative; } diff --git a/webroot/rsrc/js/application/calendar/behavior-day-view.js b/webroot/rsrc/js/application/calendar/behavior-day-view.js index ae4aaf6b6b..5be51ce99c 100644 --- a/webroot/rsrc/js/application/calendar/behavior-day-view.js +++ b/webroot/rsrc/js/application/calendar/behavior-day-view.js @@ -100,10 +100,15 @@ JX.behavior('day-view', function(config) { }, name); + var class_name = 'phui-calendar-day-event'; + if (e.canEdit) { + class_name = class_name + ' can-drag'; + } + var div = JX.$N( 'div', { - className: 'phui-calendar-day-event', + className: class_name, sigil: sigil, meta: {eventID: eventID, record: e, uri: uri}, style: { @@ -169,7 +174,11 @@ JX.behavior('day-view', function(config) { var cell_event = JX.$N( 'td', { - className: 'phui-calendar-day-events' + meta: { + time: hours[i]['hour_meridian'] + }, + className: 'phui-calendar-day-events', + sigil: 'phui-calendar-day-event-cell' }); var row = JX.$N( @@ -195,6 +204,11 @@ JX.behavior('day-view', function(config) { } + var year = config.year; + var month = config.month; + var day = config.day; + var query = config.query; + var hours = config.hours; var first_event_hour = config.firstEventHour; var first_event_hour_epoch = parseInt(config.firstEventHourEpoch, 10); @@ -234,6 +248,10 @@ JX.behavior('day-view', function(config) { if (!e.isNormalMouseEvent()) { return; } + var data = e.getNodeData('phui-calendar-day-event'); + if (!data.record.canEdit) { + return; + } e.kill(); dragging = e.getNode('phui-calendar-day-event'); JX.DOM.alterClass(dragging, 'phui-drag', true); @@ -264,12 +282,13 @@ JX.behavior('day-view', function(config) { dragging.style.top = new_top + 'px'; }); JX.Stratcom.listen('mouseup', null, function(){ - var data = JX.Stratcom.getData(dragging); - var record = data.record; - if (!dragging) { return; } + + var data = JX.Stratcom.getData(dragging); + var record = data.record; + if (new_top == offset_top) { var now = new Date(); if (now.getTime() - click_time.getTime() < 250) { @@ -304,6 +323,25 @@ JX.behavior('day-view', function(config) { } }); + JX.DOM.listen(table, 'click', 'phui-calendar-day-event-cell', function(e){ + if (!e.isNormalClick()) { + return; + } + var data = e.getNodeData('phui-calendar-day-event-cell'); + var time = data.time; + new JX.Workflow( + '/calendar/event/create/', + { + year: year, + month: month, + day: day, + time: time, + next: 'day', + query: query + }) + .start(); + }); + var hourly_events_wrapper = JX.$N( 'div', {style: { From f5c9b9c014a789d3b532da2179443a116f104dea Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 25 May 2015 19:02:49 +1000 Subject: [PATCH 09/43] Make Herald rules more resilient Summary: Make Herald conditions and actions more resilient (see discussion in D12896). This protects against invalid rules, which may have been valid in the past but are no longer valid. Specifically: - If a rule has an invalid field, the conditions fail and the actions do not execute. - The transcript shows that the rule failed because of an invalid field, and points at the issue. - If a rule has an invalid action, that action fails but other actions execute. - The transcript shows that the action failed. - Everything else (particularly, other rules) continues normally in both cases. - The edit interface is somewhat working when editing an invalid rule, but it could use some further improvements. Test Plan: # Ran this rule on a differential revision and saw the rule fail in the transcript. # Was able to submit a differential without receiving an `ERR-CONDUIT-CORE`. # Edited the Herald rule using the UI and was able to save the rule succesfully. # Ran this rule on a differential revision and saw one success and one failure in the transcript. # Was able to submit a differential without receiving an `ERR-CONDUIT-CORE`. # Edited the Herald rule using the UI. Clicking save caused a `HeraldInvalidActionException` to be thrown, but maybe this is okay. Differential Revision: http://phabricator.local/D41 --- .../herald/adapter/HeraldAdapter.php | 18 ++++++-- .../adapter/HeraldDifferentialDiffAdapter.php | 5 ++- .../controller/HeraldRuleController.php | 41 +++++++++++++++---- .../controller/HeraldTranscriptController.php | 5 ++- .../herald/engine/HeraldEngine.php | 10 +++++ 5 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 300af2c383..cd48abe79a 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -1203,7 +1203,15 @@ abstract class HeraldAdapter { $rule_global = HeraldRuleTypeConfig::RULE_TYPE_GLOBAL; $action_type = $action->getAction(); - $action_name = idx($this->getActionNameMap($rule_global), $action_type); + + $default = $this->isHeraldCustomKey($action_type) + ? pht('(Unknown Custom Action "%s") equals', $action_type) + : pht('(Unknown Action "%s") equals', $action_type); + + $action_name = idx( + $this->getActionNameMap($rule_global), + $action_type, + $default); $target = $this->renderActionTargetAsText($action, $handles); @@ -1525,7 +1533,9 @@ abstract class HeraldAdapter { $supported = $this->getActions($rule_type); $supported = array_fuse($supported); if (empty($supported[$action])) { - throw new Exception( + return new HeraldApplyTranscript( + $effect, + false, pht( 'Adapter "%s" does not support action "%s" for rule type "%s".', get_class($this), @@ -1548,7 +1558,9 @@ abstract class HeraldAdapter { $result = $this->handleCustomHeraldEffect($effect); if (!$result) { - throw new Exception( + return new HeraldApplyTranscript( + $effect, + false, pht( 'No custom action exists to handle rule action "%s".', $action)); diff --git a/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php b/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php index 766be528f5..2a7a576e3e 100644 --- a/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php +++ b/src/applications/herald/adapter/HeraldDifferentialDiffAdapter.php @@ -154,7 +154,10 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { pht('Blocked diff.')); break; default: - throw new Exception(pht('No rules to handle action "%s"!', $action)); + $result[] = new HeraldApplyTranscript( + $effect, + false, + pht('No rules to handle action "%s"!', $action)); } } diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index f52a849d1e..ed72d1eda6 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -320,7 +320,7 @@ final class HeraldRuleController extends HeraldController { try { $adapter->willSaveAction($rule, $obj); } catch (HeraldInvalidActionException $ex) { - $errors[] = $ex; + $errors[] = $ex->getMessage(); } $actions[] = $obj; @@ -354,7 +354,6 @@ final class HeraldRuleController extends HeraldController { if ($rule->getConditions()) { $serial_conditions = array(); foreach ($rule->getConditions() as $condition) { - $value = $condition->getValue(); switch ($condition->getFieldName()) { case HeraldAdapter::FIELD_TASK_PRIORITY: @@ -394,10 +393,10 @@ final class HeraldRuleController extends HeraldController { $serial_actions = array( array('default', ''), ); + if ($rule->getActions()) { $serial_actions = array(); foreach ($rule->getActions() as $action) { - switch ($action->getAction()) { case HeraldAdapter::ACTION_FLAG: case HeraldAdapter::ACTION_BLOCK: @@ -438,21 +437,39 @@ final class HeraldRuleController extends HeraldController { // names of, so that saving a rule without touching anything doesn't change // it. foreach ($rule->getConditions() as $condition) { - if (empty($field_map[$condition->getFieldName()])) { - $field_map[$condition->getFieldName()] = pht(''); + $field_name = $condition->getFieldName(); + + if (empty($field_map[$field_name])) { + $field_map[$field_name] = pht('', $field_name); } } $actions = $adapter->getActions($rule->getRuleType()); $action_map = array_select_keys($all_actions, $actions); + // Populate any actions which exist in the rule but which we don't know the + // names of, so that saving a rule without touching anything doesn't change + // it. + foreach ($rule->getActions() as $action) { + $action_name = $action->getAction(); + + if (empty($action_map[$action_name])) { + $action_map[$action_name] = pht('', $action_name); + } + } + + $config_info = array(); $config_info['fields'] = $field_map; $config_info['conditions'] = $all_conditions; $config_info['actions'] = $action_map; foreach ($config_info['fields'] as $field => $name) { - $field_conditions = $adapter->getConditionsForField($field); + try { + $field_conditions = $adapter->getConditionsForField($field); + } catch (Exception $ex) { + $field_conditions = array(HeraldAdapter::CONDITION_UNCONDITIONALLY); + } $config_info['conditionMap'][$field] = $field_conditions; } @@ -468,9 +485,15 @@ final class HeraldRuleController extends HeraldController { $config_info['rule_type'] = $rule->getRuleType(); foreach ($config_info['actions'] as $action => $name) { - $config_info['targets'][$action] = $adapter->getValueTypeForAction( - $action, - $rule->getRuleType()); + try { + $action_value = $adapter->getValueTypeForAction( + $action, + $rule->getRuleType()); + } catch (Exception $ex) { + $action_value = array(HeraldAdapter::VALUE_NONE); + } + + $config_info['targets'][$action] = $action_value; } $changeflag_options = diff --git a/src/applications/herald/controller/HeraldTranscriptController.php b/src/applications/herald/controller/HeraldTranscriptController.php index f20d620cca..a915e55b3d 100644 --- a/src/applications/herald/controller/HeraldTranscriptController.php +++ b/src/applications/herald/controller/HeraldTranscriptController.php @@ -380,7 +380,10 @@ final class HeraldTranscriptController extends HeraldController { $item->setState(PHUIObjectItemView::STATE_FAIL); } - $rule = idx($action_names, $apply_xscript->getAction(), pht('Unknown')); + $rule = idx( + $action_names, + $apply_xscript->getAction(), + pht('Unknown Action "%s"', $apply_xscript->getAction())); $item->setHeader(pht('%s: %s', $rule, $target)); $item->addAttribute($apply_xscript->getReason()); diff --git a/src/applications/herald/engine/HeraldEngine.php b/src/applications/herald/engine/HeraldEngine.php index 5f1189fff6..4e986987d5 100644 --- a/src/applications/herald/engine/HeraldEngine.php +++ b/src/applications/herald/engine/HeraldEngine.php @@ -272,6 +272,16 @@ final class HeraldEngine { $result = false; } else { foreach ($conditions as $condition) { + try { + $object->getHeraldField($condition->getFieldName()); + } catch (Exception $ex) { + $reason = pht( + 'Field "%s" does not exist!', + $condition->getFieldName()); + $result = false; + break; + } + $match = $this->doesConditionMatch($rule, $condition, $object); if (!$all && $match) { From 635ea2cbaf910f3ef9a22c0d5fa656d8c84e5fdf Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 25 May 2015 19:10:12 +1000 Subject: [PATCH 10/43] Remove arcanist projects from Herald Summary: Ref T7604. Remove arcanist projects from Herald. Depends on D12894 and D12957. Test Plan: See D12957. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: johnny-bit, Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D12896 --- resources/celerity/map.php | 26 +++++++++---------- .../herald/adapter/HeraldAdapter.php | 6 ----- .../HeraldDifferentialRevisionAdapter.php | 3 --- .../controller/HeraldRuleController.php | 1 - .../js/application/herald/HeraldRuleEditor.js | 1 - 5 files changed, 13 insertions(+), 24 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index b32f74bbad..f0f21380d4 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -331,7 +331,7 @@ return array( 'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'b1a59974', 'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'ea681761', 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', - 'rsrc/js/application/calendar/behavior-day-view.js' => '28a60488', + 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', @@ -370,7 +370,7 @@ return array( 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => 'e5822781', 'rsrc/js/application/files/behavior-icon-composer.js' => '8ef9ab58', 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', - 'rsrc/js/application/herald/HeraldRuleEditor.js' => '9229e764', + 'rsrc/js/application/herald/HeraldRuleEditor.js' => '271ffdd7', 'rsrc/js/application/herald/PathTypeahead.js' => 'f7fc67ec', 'rsrc/js/application/herald/herald-rule-editor.js' => '7ebaeed3', 'rsrc/js/application/maniphest/behavior-batch-editor.js' => 'f5d1233b', @@ -526,7 +526,7 @@ return array( 'global-drag-and-drop-css' => '697324ad', 'harbormaster-css' => '49d64eb4', 'herald-css' => '826075fa', - 'herald-rule-editor' => '9229e764', + 'herald-rule-editor' => '271ffdd7', 'herald-test-css' => '778b008e', 'homepage-panel-css' => 'e34bf140', 'inline-comment-summary-css' => 'eb5f8e8c', @@ -554,7 +554,7 @@ return array( 'javelin-behavior-dashboard-move-panels' => '82439934', 'javelin-behavior-dashboard-query-panel-select' => '453c5375', 'javelin-behavior-dashboard-tab-panel' => 'd4eecc63', - 'javelin-behavior-day-view' => '28a60488', + 'javelin-behavior-day-view' => '5c46cff2', 'javelin-behavior-device' => 'a205cf28', 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18', 'javelin-behavior-differential-comment-jump' => '4fdb476d', @@ -1000,6 +1000,15 @@ return array( 'phabricator-drag-and-drop-file-upload', 'phabricator-draggable-list', ), + '271ffdd7' => array( + 'multirow-row-manager', + 'javelin-install', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-json', + 'phabricator-prefab', + ), '2818f5ce' => array( 'javelin-install', 'javelin-util', @@ -1517,15 +1526,6 @@ return array( 'javelin-dom', 'javelin-stratcom', ), - '9229e764' => array( - 'multirow-row-manager', - 'javelin-install', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-json', - 'phabricator-prefab', - ), 93568464 => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index cd48abe79a..f494ad95fc 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -42,7 +42,6 @@ abstract class HeraldAdapter { const FIELD_APPLICATION_EMAIL = 'applicaton-email'; const FIELD_TASK_PRIORITY = 'taskpriority'; const FIELD_TASK_STATUS = 'taskstatus'; - const FIELD_ARCANIST_PROJECT = 'arcanist-project'; const FIELD_PUSHER_IS_COMMITTER = 'pusher-is-committer'; const FIELD_PATH = 'path'; @@ -100,7 +99,6 @@ abstract class HeraldAdapter { const VALUE_BUILD_PLAN = 'buildplan'; const VALUE_TASK_PRIORITY = 'taskpriority'; const VALUE_TASK_STATUS = 'taskstatus'; - const VALUE_ARCANIST_PROJECT = 'arcanistprojects'; const VALUE_LEGAL_DOCUMENTS = 'legaldocuments'; const VALUE_APPLICATION_EMAIL = 'applicationemail'; @@ -385,7 +383,6 @@ abstract class HeraldAdapter { self::FIELD_APPLICATION_EMAIL => pht('Receiving email address'), self::FIELD_TASK_PRIORITY => pht('Task priority'), self::FIELD_TASK_STATUS => pht('Task status'), - self::FIELD_ARCANIST_PROJECT => pht('Arcanist Project'), self::FIELD_PUSHER_IS_COMMITTER => pht('Pusher same as committer'), self::FIELD_PATH => pht('Path'), ) + $this->getCustomFieldNameMap(); @@ -441,7 +438,6 @@ abstract class HeraldAdapter { case self::FIELD_PUSHER: case self::FIELD_TASK_PRIORITY: case self::FIELD_TASK_STATUS: - case self::FIELD_ARCANIST_PROJECT: return array( self::CONDITION_IS_ANY, self::CONDITION_IS_NOT_ANY, @@ -946,8 +942,6 @@ abstract class HeraldAdapter { return self::VALUE_TASK_PRIORITY; case self::FIELD_TASK_STATUS: return self::VALUE_TASK_STATUS; - case self::FIELD_ARCANIST_PROJECT: - return self::VALUE_ARCANIST_PROJECT; default: return self::VALUE_USER; } diff --git a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php index ff14686636..e7ba56cf06 100644 --- a/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php +++ b/src/applications/herald/adapter/HeraldDifferentialRevisionAdapter.php @@ -80,7 +80,6 @@ final class HeraldDifferentialRevisionAdapter self::FIELD_AFFECTED_PACKAGE, self::FIELD_AFFECTED_PACKAGE_OWNER, self::FIELD_IS_NEW_OBJECT, - self::FIELD_ARCANIST_PROJECT, ), parent::getFields()); } @@ -259,8 +258,6 @@ final class HeraldDifferentialRevisionAdapter $packages = $this->loadAffectedPackages(); return PhabricatorOwnersOwner::loadAffiliatedUserPHIDs( mpull($packages, 'getID')); - case self::FIELD_ARCANIST_PROJECT: - return $this->revision->getArcanistProjectPHID(); } return parent::getHeraldField($field); diff --git a/src/applications/herald/controller/HeraldRuleController.php b/src/applications/herald/controller/HeraldRuleController.php index ed72d1eda6..a2fbe7a108 100644 --- a/src/applications/herald/controller/HeraldRuleController.php +++ b/src/applications/herald/controller/HeraldRuleController.php @@ -626,7 +626,6 @@ final class HeraldRuleController extends HeraldController { 'taskpriority' => new ManiphestTaskPriorityDatasource(), 'taskstatus' => new ManiphestTaskStatusDatasource(), 'buildplan' => new HarbormasterBuildPlanDatasource(), - 'arcanistprojects' => new DiffusionArcanistProjectDatasource(), 'package' => new PhabricatorOwnersPackageDatasource(), 'project' => new PhabricatorProjectDatasource(), 'user' => new PhabricatorPeopleDatasource(), diff --git a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js index abf645ba29..e7300528d6 100644 --- a/webroot/rsrc/js/application/herald/HeraldRuleEditor.js +++ b/webroot/rsrc/js/application/herald/HeraldRuleEditor.js @@ -220,7 +220,6 @@ JX.install('HeraldRuleEditor', { case 'buildplan': case 'taskpriority': case 'taskstatus': - case 'arcanistprojects': case 'legaldocuments': case 'applicationemail': var tokenizer = this._newTokenizer(type); From d6817d00caf895ef6acb5ca144631dbd0ab571dd Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 25 May 2015 20:50:23 +1000 Subject: [PATCH 11/43] Add encoding information to "repository.query" Summary: Ref T7604. Add `encoding` information to the `repository.query` #conduit query. Test Plan: Changed repository encoding from the UI and saw the encoding returned by the Conduit call. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D12992 --- src/applications/repository/storage/PhabricatorRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 547fdb484d..14cc11c01d 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -135,6 +135,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'isActive' => $this->isTracked(), 'isHosted' => $this->isHosted(), 'isImporting' => $this->isImporting(), + 'encoding' => $this->getDetail('encoding'), ); } From 1b12249b2ca3bed1edcc5bf163e473915ab28546 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 25 May 2015 21:28:49 +1000 Subject: [PATCH 12/43] Fix some format strings Summary: These format strings use `%d` instead of `%s`. Test Plan: Eyeball it. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12996 --- .../differential/parser/DifferentialChangesetParser.php | 3 +-- .../diffusion/controller/DiffusionCommitController.php | 4 ++-- .../PhabricatorRepositoryManagementReparseWorkflow.php | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php index bca4098ca1..a34fe85064 100644 --- a/src/applications/differential/parser/DifferentialChangesetParser.php +++ b/src/applications/differential/parser/DifferentialChangesetParser.php @@ -906,11 +906,10 @@ final class DifferentialChangesetParser { $shield = $renderer->renderShield( pht('This file was completely deleted.')); } else if ($this->changeset->getAffectedLineCount() > 2500) { - $lines = number_format($this->changeset->getAffectedLineCount()); $shield = $renderer->renderShield( pht( 'This file has a very large number of changes (%s lines).', - $lines)); + new PhutilNumber($this->changeset->getAffectedLineCount()))); } } diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php index b30cfb0680..7a795e1099 100644 --- a/src/applications/diffusion/controller/DiffusionCommitController.php +++ b/src/applications/diffusion/controller/DiffusionCommitController.php @@ -240,10 +240,10 @@ final class DiffusionCommitController extends DiffusionController { $change_panel = new PHUIObjectBoxView(); $header = new PHUIHeaderView(); - $header->setHeader(pht('Changes (%d', number_format($count))); + $header->setHeader(pht('Changes (%s)', new PhutilNumber($count))); $change_panel->setID('toc'); - if ($count > self::CHANGES_LIMIT && !$show_all_details) { + if ($count > self::CHANGES_LIMIT && !$show_all_details) { $icon = id(new PHUIIconView()) ->setIconFont('fa-files-o'); diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php index 275577ea33..e06c6ca089 100644 --- a/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php +++ b/src/applications/repository/management/PhabricatorRepositoryManagementReparseWorkflow.php @@ -241,8 +241,8 @@ final class PhabricatorRepositoryManagementReparseWorkflow '**NOTE**: This script will queue tasks to reparse the data. Once the '. 'tasks have been queued, you need to run Taskmaster daemons to '. 'execute them.'."\n\n". - "QUEUEING TASKS (%d Commits):", - number_format(count($commits)))); + "QUEUEING TASKS (%s Commits):", + new PhutilNumber(count($commits)))); } $progress = new PhutilConsoleProgressBar(); From f0d16b3047ff541aabf3f2f1ee9bd02aabdab7c6 Mon Sep 17 00:00:00 2001 From: Fabian Stelzer Date: Mon, 25 May 2015 04:49:12 -0700 Subject: [PATCH 13/43] show merged commits in herald emails for merge commits Summary: Includes a new Block in Herad emails which tells the user about which commits were merged in a merge commit Otherwise the email would just say "Merge branch XYZ". Ref T8295 Test Plan: imported various commits (and merges) and watched resulting herald emails for all of them Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8295 Differential Revision: https://secure.phabricator.com/D12993 --- src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionConfigOptions.php | 22 +++++++ .../PhabricatorCommitMergedCommitsField.php | 66 +++++++++++++++++++ .../storage/PhabricatorRepositoryCommit.php | 4 +- 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index bb4a613cae..44bf8afe24 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1541,6 +1541,7 @@ phutil_register_library_map(array( 'PhabricatorClusterConfigOptions' => 'applications/config/option/PhabricatorClusterConfigOptions.php', 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', 'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php', + 'PhabricatorCommitMergedCommitsField' => 'applications/repository/customfield/PhabricatorCommitMergedCommitsField.php', 'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php', 'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php', 'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php', @@ -4906,6 +4907,7 @@ phutil_register_library_map(array( 'PhabricatorClusterConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitCustomField' => 'PhabricatorCustomField', + 'PhabricatorCommitMergedCommitsField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField', 'PhabricatorCommonPasswords' => 'Phobject', diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php index 28a9e1b670..89a20fac68 100644 --- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php +++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php @@ -20,6 +20,21 @@ final class PhabricatorDiffusionConfigOptions } public function getOptions() { + $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; + + $fields = array( + new PhabricatorCommitBranchesField(), + new PhabricatorCommitTagsField(), + new PhabricatorCommitMergedCommitsField(), + ); + + $default_fields = array(); + foreach ($fields as $field) { + $default_fields[$field->getFieldKey()] = array( + 'disabled' => $field->shouldDisableByDefault(), + ); + } + return array( $this->newOption( 'metamta.diffusion.subject-prefix', @@ -124,6 +139,13 @@ final class PhabricatorDiffusionConfigOptions 'from web traffic (for example, if you use different SSH and '. 'web load balancers), you can set the SSH hostname here. This '. 'is an advanced option.')), + $this->newOption('diffusion.fields', $custom_field_type, $default_fields) + ->setCustomData( + id(new PhabricatorRepositoryCommit()) + ->getCustomFieldBaseClass()) + ->setDescription(pht( + "Select and reorder diffusion fields.\n\n". + "These will primarily show up in Mail Notifications.")), ); } diff --git a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php new file mode 100644 index 0000000000..57a0dd9bdb --- /dev/null +++ b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php @@ -0,0 +1,66 @@ +getObject(); + + try { + $merges = DiffusionPathChange::newFromConduit( + id(new ConduitCall('diffusion.mergedcommitsquery', array( + 'commit' => $commit->getCommitIdentifier(), + 'limit' => $limit + 1, + 'callsign' => $commit->getRepository()->getCallsign(), + ))) + ->setUser($this->getViewer()) + ->execute()); + + if (count($merges) > $limit) { + $merges = array_slice($merges, 0, $limit); + $merges_caption = + pht("This commit merges more than %d changes. Only the first ". + "%d are shown.\n", $limit, $limit); + } + + if ($merges) { + $merge_commits = array(); + foreach ($merges as $merge) { + $merge_commits[] = $merge->getAuthorName(). + ': '. + $merge->getSummary(); + } + $body->addTextSection( + pht('MERGED COMMITS'), + $merges_caption.implode("\n", $merge_commits)); + } + } catch (ConduitException $ex) { + // Log the exception into the email body + $body->addTextSection( + pht('MERGED COMMITS'), + pht('Error generating merged commits: ').$ex->getMessage()); + } + + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index a3b4bf0609..18328758ba 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -349,9 +349,7 @@ final class PhabricatorRepositoryCommit public function getCustomFieldSpecificationForRole($role) { - // TODO: We could make this configurable eventually, but just use the - // defaults for now. - return array(); + return PhabricatorEnv::getEnvConfig('diffusion.fields'); } public function getCustomFieldBaseClass() { From 969a6a2b38b68618bda26a7f16a94d99addf2538 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 25 May 2015 05:34:23 -0700 Subject: [PATCH 14/43] Fix custom field selector control in cases where the user doesn't edit anything Summary: The JS and PHP representations of state can differ; just have the JS write the state out immediately on page load. Test Plan: Saved `diffusion.fields` without making changes, reloaded, saw no effective change. Reviewers: joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D12998 --- resources/celerity/map.php | 18 +++++++++--------- .../PhabricatorCustomFieldConfigOptionType.php | 5 +---- .../config/behavior-reorder-fields.js | 1 + 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f0f21380d4..e093be5ae8 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -333,7 +333,7 @@ return array( 'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18', 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', - 'rsrc/js/application/config/behavior-reorder-fields.js' => '14a827de', + 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '16c695bf', @@ -543,7 +543,7 @@ return array( 'javelin-behavior-audio-source' => '59b251eb', 'javelin-behavior-audit-preview' => 'd835b03a', 'javelin-behavior-choose-control' => '6153c708', - 'javelin-behavior-config-reorder-fields' => '14a827de', + 'javelin-behavior-config-reorder-fields' => 'b6993408', 'javelin-behavior-conpherence-drag-and-drop-photo' => 'cf86d16a', 'javelin-behavior-conpherence-menu' => 'c0348cac', 'javelin-behavior-conpherence-pontificate' => '21ba5861', @@ -907,13 +907,6 @@ return array( 'javelin-dom', 'javelin-history', ), - '14a827de' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-json', - 'phabricator-draggable-list', - ), '14ac66f5' => array( 'javelin-install', 'javelin-dom', @@ -1728,6 +1721,13 @@ return array( 'javelin-dom', 'javelin-util', ), + 'b6993408' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-json', + 'phabricator-draggable-list', + ), 'ba4fa35c' => array( 'javelin-behavior', 'javelin-dom', diff --git a/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php b/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php index 6ff9eae1d0..e062d086d0 100644 --- a/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php +++ b/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php @@ -12,9 +12,6 @@ final class PhabricatorCustomFieldConfigOptionType $storage_value = $request->getStr('value'); $in_value = phutil_json_decode($storage_value); - if (!is_array($in_value)) { - $in_value = array(); - } // When we submit from JS, we submit a list (since maps are not guaranteed // to retain order). Convert it into a map for storage (since it's far more @@ -113,7 +110,7 @@ final class PhabricatorCustomFieldConfigOptionType 'id' => $input_id, 'type' => 'hidden', 'name' => 'value', - 'value' => json_encode($display_value), + 'value' => '', )); Javelin::initBehavior( diff --git a/webroot/rsrc/js/application/config/behavior-reorder-fields.js b/webroot/rsrc/js/application/config/behavior-reorder-fields.js index 948b248afe..1c041e85f8 100644 --- a/webroot/rsrc/js/application/config/behavior-reorder-fields.js +++ b/webroot/rsrc/js/application/config/behavior-reorder-fields.js @@ -54,4 +54,5 @@ JX.behavior('config-reorder-fields', function(config) { JX.$(config.inputID).value = JX.JSON.stringify(order); }; + write_state_to_form(); }); From 3d2c6f1ed27d976080d18eeedff9f9fec23f252f Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 25 May 2015 05:34:41 -0700 Subject: [PATCH 15/43] Make "Tags" and "Branches" custom fields work properly in Diffusion Summary: Ref T8295 (vaguely related). This old method has no callsites. Test Plan: Generated mail, saw "Tags" and "Branches". Reviewers: fabe, joshuaspence Reviewed By: fabe, joshuaspence Subscribers: epriestley Maniphest Tasks: T8295 Differential Revision: https://secure.phabricator.com/D13000 --- .../PhabricatorCommitBranchesField.php | 9 +++++---- .../customfield/PhabricatorCommitTagsField.php | 9 +++++---- .../customfield/field/PhabricatorCustomField.php | 16 ---------------- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php index ed07a8fb29..c72c51715e 100644 --- a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php +++ b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php @@ -7,13 +7,14 @@ final class PhabricatorCommitBranchesField return 'diffusion:branches'; } - public function shouldAppearInApplicationTransactions() { + public function shouldAppearInTransactionMail() { return true; } - public function buildApplicationTransactionMailBody( - PhabricatorApplicationTransaction $xaction, - PhabricatorMetaMTAMailBody $body) { + public function updateTransactionMailBody( + PhabricatorMetaMTAMailBody $body, + PhabricatorApplicationTransactionEditor $editor, + array $xactions) { $params = array( 'contains' => $this->getObject()->getCommitIdentifier(), diff --git a/src/applications/repository/customfield/PhabricatorCommitTagsField.php b/src/applications/repository/customfield/PhabricatorCommitTagsField.php index 25b2a4ee4e..1220f6be63 100644 --- a/src/applications/repository/customfield/PhabricatorCommitTagsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitTagsField.php @@ -7,13 +7,14 @@ final class PhabricatorCommitTagsField return 'diffusion:tags'; } - public function shouldAppearInApplicationTransactions() { + public function shouldAppearInTransactionMail() { return true; } - public function buildApplicationTransactionMailBody( - PhabricatorApplicationTransaction $xaction, - PhabricatorMetaMTAMailBody $body) { + public function updateTransactionMailBody( + PhabricatorMetaMTAMailBody $body, + PhabricatorApplicationTransactionEditor $editor, + array $xactions) { $params = array( 'commit' => $this->getObject()->getCommitIdentifier(), diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index 28110e7334..a844ac7735 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -1055,22 +1055,6 @@ abstract class PhabricatorCustomField { return false; } - /** - * TODO: this is only used by Diffusion right now and everything is completely - * faked since Diffusion doesn't use ApplicationTransactions yet. This should - * get fleshed out as we have more use cases. - * - * @task appxaction - */ - public function buildApplicationTransactionMailBody( - PhabricatorApplicationTransaction $xaction, - PhabricatorMetaMTAMailBody $body) { - if ($this->proxy) { - return $this->proxy->buildApplicationTransactionMailBody($xaction, $body); - } - return; - } - /* -( Transaction Mail )--------------------------------------------------- */ From 6079629038186ea2a0a20ee44ad88642953ed6cd Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 25 May 2015 22:35:15 +1000 Subject: [PATCH 16/43] Remove "project.name" from `.arcconfig` Summary: Ref T7604. Remove the `project.name` configuration from `.arcconfig`. Test Plan: N/A Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D13001 --- .arcconfig | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.arcconfig b/.arcconfig index 70a43b9d9a..e6aa540bd3 100644 --- a/.arcconfig +++ b/.arcconfig @@ -1,6 +1,5 @@ { - "project.name" : "phabricator", - "phabricator.uri" : "https://secure.phabricator.com/", - "unit.engine" : "PhutilUnitTestEngine", - "load" : ["src/"] + "phabricator.uri": "https://secure.phabricator.com/", + "unit.engine": "PhutilUnitTestEngine", + "load": ["src/"] } From be19b31a64446bebc570eae245b45118241924e5 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 25 May 2015 22:43:42 +1000 Subject: [PATCH 17/43] Add repository to audit emails Summary: This is vaguely related to T5791. Add a "REPOSITORY" header to audit emails so that they can be filtered in Gmail. Test Plan: Commented on an audit and used `./bin/mail show-outbound` to inspect outbound email. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: fabe, Korvin, epriestley Differential Revision: https://secure.phabricator.com/D12997 --- src/__phutil_library_map__.php | 2 ++ .../PhabricatorDiffusionConfigOptions.php | 10 ++++--- .../PhabricatorCommitRepositoryField.php | 30 +++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 src/applications/repository/customfield/PhabricatorCommitRepositoryField.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 44bf8afe24..9373d989fe 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1542,6 +1542,7 @@ phutil_register_library_map(array( 'PhabricatorCommitBranchesField' => 'applications/repository/customfield/PhabricatorCommitBranchesField.php', 'PhabricatorCommitCustomField' => 'applications/repository/customfield/PhabricatorCommitCustomField.php', 'PhabricatorCommitMergedCommitsField' => 'applications/repository/customfield/PhabricatorCommitMergedCommitsField.php', + 'PhabricatorCommitRepositoryField' => 'applications/repository/customfield/PhabricatorCommitRepositoryField.php', 'PhabricatorCommitSearchEngine' => 'applications/audit/query/PhabricatorCommitSearchEngine.php', 'PhabricatorCommitTagsField' => 'applications/repository/customfield/PhabricatorCommitTagsField.php', 'PhabricatorCommonPasswords' => 'applications/auth/constants/PhabricatorCommonPasswords.php', @@ -4908,6 +4909,7 @@ phutil_register_library_map(array( 'PhabricatorCommitBranchesField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitCustomField' => 'PhabricatorCustomField', 'PhabricatorCommitMergedCommitsField' => 'PhabricatorCommitCustomField', + 'PhabricatorCommitRepositoryField' => 'PhabricatorCommitCustomField', 'PhabricatorCommitSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorCommitTagsField' => 'PhabricatorCommitCustomField', 'PhabricatorCommonPasswords' => 'Phobject', diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php index 89a20fac68..31b3c358e7 100644 --- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php +++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php @@ -23,6 +23,7 @@ final class PhabricatorDiffusionConfigOptions $custom_field_type = 'custom:PhabricatorCustomFieldConfigOptionType'; $fields = array( + new PhabricatorCommitRepositoryField(), new PhabricatorCommitBranchesField(), new PhabricatorCommitTagsField(), new PhabricatorCommitMergedCommitsField(), @@ -142,10 +143,11 @@ final class PhabricatorDiffusionConfigOptions $this->newOption('diffusion.fields', $custom_field_type, $default_fields) ->setCustomData( id(new PhabricatorRepositoryCommit()) - ->getCustomFieldBaseClass()) - ->setDescription(pht( - "Select and reorder diffusion fields.\n\n". - "These will primarily show up in Mail Notifications.")), + ->getCustomFieldBaseClass()) + ->setDescription( + pht( + "Select and reorder diffusion fields.\n\n". + "These will primarily show up in Mail Notifications.")), ); } diff --git a/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php b/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php new file mode 100644 index 0000000000..1a69bd5f62 --- /dev/null +++ b/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php @@ -0,0 +1,30 @@ +getObject()->getRepository(); + + $body->addTextSection( + pht('REPOSITORY'), + $repository->getMonogram().' '.$repository->getName()); + } + +} From 0c967dd53d57e8c0caf13c305f7c4d4fe7181880 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Mon, 25 May 2015 22:52:41 +1000 Subject: [PATCH 18/43] Mark "arcanist.projectinfo" as deprecated Summary: Ref T7604. Mark the `arcanist.projectinfo` #conduit method as deprecated. Test Plan: Went to the `/config/issue/` page and saw the `Deprecated Conduit Method "arcanist.projectinfo" In Use` setup issue. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D12970 --- .../conduit/ArcanistProjectInfoConduitAPIMethod.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php b/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php index ee5cbbff2b..c89bcf7845 100644 --- a/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php +++ b/src/applications/arcanist/conduit/ArcanistProjectInfoConduitAPIMethod.php @@ -7,8 +7,12 @@ final class ArcanistProjectInfoConduitAPIMethod return 'arcanist.projectinfo'; } + public function getMethodStatus() { + return self::METHOD_STATUS_DEPRECATED; + } + public function getMethodDescription() { - return pht('Get information about Arcanist projects.'); + return pht('Arcanist projects are deprecated.'); } protected function defineParamTypes() { From ea131bb2efb9d7e9ab6c03b499f104f88c10e09c Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 25 May 2015 07:18:48 -0700 Subject: [PATCH 19/43] Improve UX for Diffusion mail fields Summary: - Give the fields names and descriptions. - When new, default-disabled fields are added, disable them by default even if there's already a config. - Be a bit less hacky about `$faux_spec`. Test Plan: {F432383} Reviewers: joshuaspence, fabe Reviewed By: fabe Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D13006 --- .../PhabricatorDiffusionConfigOptions.php | 4 +-- .../PhabricatorCommitBranchesField.php | 8 +++++ .../PhabricatorCommitMergedCommitsField.php | 8 +++++ .../PhabricatorCommitRepositoryField.php | 8 +++++ .../PhabricatorCommitTagsField.php | 8 +++++ ...PhabricatorCustomFieldConfigOptionType.php | 17 +++++------ .../field/PhabricatorCustomField.php | 30 ++++++++++++++----- 7 files changed, 62 insertions(+), 21 deletions(-) diff --git a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php index 31b3c358e7..9c3d7eff81 100644 --- a/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php +++ b/src/applications/diffusion/config/PhabricatorDiffusionConfigOptions.php @@ -145,9 +145,7 @@ final class PhabricatorDiffusionConfigOptions id(new PhabricatorRepositoryCommit()) ->getCustomFieldBaseClass()) ->setDescription( - pht( - "Select and reorder diffusion fields.\n\n". - "These will primarily show up in Mail Notifications.")), + pht('Select and reorder Diffusion fields.')), ); } diff --git a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php index c72c51715e..0d430f36a6 100644 --- a/src/applications/repository/customfield/PhabricatorCommitBranchesField.php +++ b/src/applications/repository/customfield/PhabricatorCommitBranchesField.php @@ -7,6 +7,14 @@ final class PhabricatorCommitBranchesField return 'diffusion:branches'; } + public function getFieldName() { + return pht('Branches'); + } + + public function getFieldDescription() { + return pht('Shows branches a commit appears on in email.'); + } + public function shouldAppearInTransactionMail() { return true; } diff --git a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php index 57a0dd9bdb..267b440dce 100644 --- a/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitMergedCommitsField.php @@ -7,6 +7,14 @@ final class PhabricatorCommitMergedCommitsField return 'diffusion:mergedcommits'; } + public function getFieldName() { + return pht('Merged Commits'); + } + + public function getFieldDescription() { + return pht('For merge commits, shows merged changes in email.'); + } + public function shouldDisableByDefault() { return true; } diff --git a/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php b/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php index 1a69bd5f62..c600a8cb6c 100644 --- a/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php +++ b/src/applications/repository/customfield/PhabricatorCommitRepositoryField.php @@ -7,6 +7,14 @@ final class PhabricatorCommitRepositoryField return 'diffusion:repository'; } + public function getFieldName() { + return pht('Repository'); + } + + public function getFieldDescription() { + return pht('Shows repository in email.'); + } + public function shouldDisableByDefault() { return true; } diff --git a/src/applications/repository/customfield/PhabricatorCommitTagsField.php b/src/applications/repository/customfield/PhabricatorCommitTagsField.php index 1220f6be63..b69d246d85 100644 --- a/src/applications/repository/customfield/PhabricatorCommitTagsField.php +++ b/src/applications/repository/customfield/PhabricatorCommitTagsField.php @@ -7,6 +7,14 @@ final class PhabricatorCommitTagsField return 'diffusion:tags'; } + public function getFieldName() { + return pht('Tags'); + } + + public function getFieldDescription() { + return pht('Shows commit tags in email.'); + } + public function shouldAppearInTransactionMail() { return true; } diff --git a/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php b/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php index e062d086d0..1de963c177 100644 --- a/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php +++ b/src/infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php @@ -34,20 +34,16 @@ final class PhabricatorCustomFieldConfigOptionType $field_spec = PhabricatorEnv::getEnvConfig($option->getKey()); } - // Get all of the fields (including disabled fields) by querying for them - // with a faux spec where no fields are disabled. - $faux_spec = $field_spec; - foreach ($faux_spec as $key => $spec) { - unset($faux_spec[$key]['disabled']); - } - // TODO: We might need to build a real object here eventually. $faux_object = null; $fields = PhabricatorCustomField::buildFieldList( $field_base_class, - $faux_spec, - $faux_object); + $field_spec, + $faux_object, + array( + 'withDisabled' => true, + )); $list_id = celerity_generate_unique_node_id(); $input_id = celerity_generate_unique_node_id(); @@ -63,7 +59,8 @@ final class PhabricatorCustomFieldConfigOptionType ->addAttribute($field->getFieldDescription()) ->setHeader($field->getFieldName()); - $is_disabled = !empty($field_spec[$key]['disabled']); + $spec = idx($field_spec, $key, array()); + $is_disabled = idx($spec, 'disabled', $field->shouldDisableByDefault()); $disabled_item = clone $item; $enabled_item = clone $item; diff --git a/src/infrastructure/customfield/field/PhabricatorCustomField.php b/src/infrastructure/customfield/field/PhabricatorCustomField.php index a844ac7735..b154e0e065 100644 --- a/src/infrastructure/customfield/field/PhabricatorCustomField.php +++ b/src/infrastructure/customfield/field/PhabricatorCustomField.php @@ -105,7 +105,18 @@ abstract class PhabricatorCustomField { /** * @task apps */ - public static function buildFieldList($base_class, array $spec, $object) { + public static function buildFieldList( + $base_class, + array $spec, + $object, + array $options = array()) { + + PhutilTypeSpec::checkMap( + $options, + array( + 'withDisabled' => 'optional bool', + )); + $field_objects = id(new PhutilSymbolLoader()) ->setAncestorClass($base_class) ->loadObjects(); @@ -135,13 +146,16 @@ abstract class PhabricatorCustomField { $fields = array_select_keys($fields, array_keys($spec)) + $fields; - foreach ($spec as $key => $config) { - if (empty($fields[$key])) { - continue; - } - if (!empty($config['disabled'])) { - if ($fields[$key]->canDisableField()) { - unset($fields[$key]); + if (empty($options['withDisabled'])) { + foreach ($fields as $key => $field) { + $config = idx($spec, $key, array()) + array( + 'disabled' => $field->shouldDisableByDefault(), + ); + + if (!empty($config['disabled'])) { + if ($field->canDisableField()) { + unset($fields[$key]); + } } } } From e4c9914697037ffb83b904e1ee34a1ccb6293531 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 25 May 2015 11:19:57 -0700 Subject: [PATCH 20/43] Multi-day events should be correctly calculated for new js layout Summary: Fixes T8304, Multi-day events should be correctly calculated for new js layout Test Plan: A day with events starting before midnight of the displayed day should be correctly placed and fitted for day view. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8304 Differential Revision: https://secure.phabricator.com/D13008 --- src/infrastructure/time/PhabricatorTime.php | 6 ++++++ src/view/phui/calendar/PHUICalendarDayView.php | 12 ++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/infrastructure/time/PhabricatorTime.php b/src/infrastructure/time/PhabricatorTime.php index e31222cf76..2adcc95d49 100644 --- a/src/infrastructure/time/PhabricatorTime.php +++ b/src/infrastructure/time/PhabricatorTime.php @@ -72,4 +72,10 @@ final class PhabricatorTime { return $today; } + public static function getDateTimeFromEpoch($epoch, PhabricatorUser $viewer) { + $datetime = new DateTime('@'.$epoch); + $datetime->setTimeZone($viewer->getTimeZone()); + return $datetime; + } + } diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 2187d08341..9bdab3fc4b 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -103,8 +103,16 @@ final class PHUICalendarDayView extends AphrontView { $event->getEpochEnd() > $day_start_epoch) { if ($first_event_hour === null) { - $first_event_hour = new DateTime('@'.$event->getEpochStart()); - $first_event_hour->setTimeZone($viewer->getTimeZone()); + $first_event_hour = PhabricatorTime::getDateTimeFromEpoch( + $event->getEpochStart(), + $viewer); + + $midnight = $this->getDateTime()->setTime(0, 0, 0); + + if ($first_event_hour->format('U') < $midnight->format('U')) { + $first_event_hour = clone $midnight; + } + $eight_am = $this->getDateTime()->setTime(8, 0, 0); if ($eight_am->format('U') < $first_event_hour->format('U')) { $first_event_hour = clone $eight_am; From e8dbdedbd490fac7a237d55d6192681270828237 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 25 May 2015 14:02:33 -0700 Subject: [PATCH 21/43] Convert date control dropdowns to an input for date Summary: Ref T8060, Convert date control dropdowns to an input for date Test Plan: Create new Calendar event, use US time format to enter a date or use datepicker, confirm dates are interpreted correctly. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8060 Differential Revision: https://secure.phabricator.com/D13010 --- .../form/control/AphrontFormDateControl.php | 133 ++++-------------- .../control/AphrontFormDateControlValue.php | 60 +++----- .../phui/calendar/PHUICalendarDayView.php | 5 +- .../phui/calendar/PHUICalendarListView.php | 9 +- .../rsrc/js/core/behavior-fancy-datepicker.js | 63 ++++++--- 5 files changed, 100 insertions(+), 170 deletions(-) diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index d829a29093..ca6d87b26c 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -5,9 +5,7 @@ final class AphrontFormDateControl extends AphrontFormControl { private $initialTime; private $zone; - private $valueDay; - private $valueMonth; - private $valueYear; + private $valueDate; private $valueTime; private $allowNull; private $continueOnInvalidDate = false; @@ -41,9 +39,7 @@ final class AphrontFormDateControl extends AphrontFormControl { } public function readValueFromRequest(AphrontRequest $request) { - $day = $request->getInt($this->getDayInputName()); - $month = $request->getInt($this->getMonthInputName()); - $year = $request->getInt($this->getYearInputName()); + $date = $request->getStr($this->getDateInputName()); $time = $request->getStr($this->getTimeInputName()); $enabled = $request->getBool($this->getCheckboxInputName()); @@ -55,10 +51,8 @@ final class AphrontFormDateControl extends AphrontFormControl { $err = $this->getError(); - if ($day || $month || $year || $time) { - $this->valueDay = $day; - $this->valueMonth = $month; - $this->valueYear = $year; + if ($date || $time) { + $this->valueDate = $date; $this->valueTime = $time; // Assume invalid. @@ -67,8 +61,8 @@ final class AphrontFormDateControl extends AphrontFormControl { $zone = $this->getTimezone(); try { - $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone); - $value = $date->format('U'); + $datetime = new DateTime("{$date} {$time}", $zone); + $value = $datetime->format('U'); } catch (Exception $ex) { $value = null; } @@ -100,9 +94,7 @@ final class AphrontFormDateControl extends AphrontFormControl { public function setValue($epoch) { if ($epoch instanceof AphrontFormDateControlValue) { $this->continueOnInvalidDate = true; - $this->valueYear = $epoch->getValueYear(); - $this->valueMonth = $epoch->getValueMonth(); - $this->valueDay = $epoch->getValueDay(); + $this->valueDate = $epoch->getValueDate(); $this->valueTime = $epoch->getValueTime(); $this->allowNull = $epoch->getOptional(); $this->isDisabled = $epoch->isDisabled(); @@ -119,42 +111,18 @@ final class AphrontFormDateControl extends AphrontFormControl { $readable = $this->formatTime($epoch, 'Y!m!d!g:i A'); $readable = explode('!', $readable, 4); - $this->valueYear = $readable[0]; - $this->valueMonth = $readable[1]; - $this->valueDay = $readable[2]; + $year = $readable[0]; + $month = $readable[1]; + $day = $readable[2]; + + $this->valueDate = $month.'/'.$day.'/'.$year; $this->valueTime = $readable[3]; return $result; } - private function getMinYear() { - $cur_year = $this->formatTime( - time(), - 'Y'); - $val_year = $this->getYearInputValue(); - - return min($cur_year, $val_year) - 3; - } - - private function getMaxYear() { - $cur_year = $this->formatTime( - time(), - 'Y'); - $val_year = $this->getYearInputValue(); - - return max($cur_year, $val_year) + 3; - } - - private function getDayInputValue() { - return $this->valueDay; - } - - private function getMonthInputValue() { - return $this->valueMonth; - } - - private function getYearInputValue() { - return $this->valueYear; + private function getDateInputValue() { + return $this->valueDate; } private function getTimeInputValue() { @@ -168,18 +136,10 @@ final class AphrontFormDateControl extends AphrontFormControl { $fmt); } - private function getDayInputName() { + private function getDateInputName() { return $this->getName().'_d'; } - private function getMonthInputName() { - return $this->getName().'_m'; - } - - private function getYearInputName() { - return $this->getName().'_y'; - } - private function getTimeInputName() { return $this->getName().'_t'; } @@ -202,27 +162,6 @@ final class AphrontFormDateControl extends AphrontFormControl { $disabled = 'disabled'; } - $min_year = $this->getMinYear(); - $max_year = $this->getMaxYear(); - - $days = range(1, 31); - $days = array_fuse($days); - - $months = array( - 1 => pht('Jan'), - 2 => pht('Feb'), - 3 => pht('Mar'), - 4 => pht('Apr'), - 5 => pht('May'), - 6 => pht('Jun'), - 7 => pht('Jul'), - 8 => pht('Aug'), - 9 => pht('Sep'), - 10 => pht('Oct'), - 11 => pht('Nov'), - 12 => pht('Dec'), - ); - $checkbox = null; if ($this->allowNull) { $checkbox = javelin_tag( @@ -237,32 +176,24 @@ final class AphrontFormDateControl extends AphrontFormControl { )); } - $years = range($this->getMinYear(), $this->getMaxYear()); - $years = array_fuse($years); - - $days_sel = AphrontFormSelectControl::renderSelectTag( - $this->getDayInputValue(), - $days, + $date_sel = javelin_tag( + 'input', array( - 'name' => $this->getDayInputName(), - 'sigil' => 'day-input', - )); + 'autocomplete' => 'off', + 'name' => $this->getDateInputName(), + 'sigil' => 'date-input', + 'value' => $this->getDateInputValue(), + 'type' => 'text', + 'class' => 'aphront-form-date-time-input', + ), + ''); - $months_sel = AphrontFormSelectControl::renderSelectTag( - $this->getMonthInputValue(), - $months, + $date_div = javelin_tag( + 'div', array( - 'name' => $this->getMonthInputName(), - 'sigil' => 'month-input', - )); - - $years_sel = AphrontFormSelectControl::renderSelectTag( - $this->getYearInputValue(), - $years, - array( - 'name' => $this->getYearInputName(), - 'sigil' => 'year-input', - )); + 'class' => 'aphront-form-date-time-input-container', + ), + $date_sel); $cicon = id(new PHUIIconView()) ->setIconFont('fa-calendar'); @@ -329,9 +260,7 @@ final class AphrontFormDateControl extends AphrontFormControl { ), array( $checkbox, - $days_sel, - $months_sel, - $years_sel, + $date_div, $cal_icon, $time_div, )); diff --git a/src/view/form/control/AphrontFormDateControlValue.php b/src/view/form/control/AphrontFormDateControlValue.php index 28d52371a9..43fb42664e 100644 --- a/src/view/form/control/AphrontFormDateControlValue.php +++ b/src/view/form/control/AphrontFormDateControlValue.php @@ -2,9 +2,7 @@ final class AphrontFormDateControlValue extends Phobject { - private $valueDay; - private $valueMonth; - private $valueYear; + private $valueDate; private $valueTime; private $valueEnabled; @@ -12,16 +10,8 @@ final class AphrontFormDateControlValue extends Phobject { private $zone; private $optional; - public function getValueDay() { - return $this->valueDay; - } - - public function getValueMonth() { - return $this->valueMonth; - } - - public function getValueYear() { - return $this->valueYear; + public function getValueDate() { + return $this->valueDate; } public function getValueTime() { @@ -36,15 +26,7 @@ final class AphrontFormDateControlValue extends Phobject { } public function isEmpty() { - if ($this->valueDay) { - return false; - } - - if ($this->valueMonth) { - return false; - } - - if ($this->valueYear) { + if ($this->valueDate) { return false; } @@ -83,9 +65,7 @@ final class AphrontFormDateControlValue extends Phobject { $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; - $value->valueYear = $year; - $value->valueMonth = $month; - $value->valueDay = $day; + $value->valueDate = $month.'/'.$day.'/'.$year; $value->valueTime = coalesce($time, '12:00 AM'); $value->valueEnabled = $enabled; @@ -95,10 +75,7 @@ final class AphrontFormDateControlValue extends Phobject { public static function newFromRequest(AphrontRequest $request, $key) { $value = new AphrontFormDateControlValue(); $value->viewer = $request->getViewer(); - - $value->valueDay = $request->getInt($key.'_d'); - $value->valueMonth = $request->getInt($key.'_m'); - $value->valueYear = $request->getInt($key.'_y'); + $value->valueDate = $request->getStr($key.'_d'); $value->valueTime = $request->getStr($key.'_t'); $value->valueEnabled = $request->getStr($key.'_e'); @@ -111,11 +88,14 @@ final class AphrontFormDateControlValue extends Phobject { $readable = $value->formatTime($epoch, 'Y!m!d!g:i A'); $readable = explode('!', $readable, 4); - $value->valueYear = $readable[0]; - $value->valueMonth = $readable[1]; - $value->valueDay = $readable[2]; + $year = $readable[0]; + $month = $readable[1]; + $day = $readable[2]; + + $value->valueDate = $month.'/'.$day.'/'.$year; $value->valueTime = $readable[3]; + return $value; } @@ -125,9 +105,7 @@ final class AphrontFormDateControlValue extends Phobject { $value = new AphrontFormDateControlValue(); $value->viewer = $viewer; - $value->valueYear = idx($dictionary, 'y'); - $value->valueMonth = idx($dictionary, 'm'); - $value->valueDay = idx($dictionary, 'd'); + $value->valueDate = idx($dictionary, 'd'); $value->valueTime = idx($dictionary, 't'); $value->valueEnabled = idx($dictionary, 'e'); @@ -149,9 +127,7 @@ final class AphrontFormDateControlValue extends Phobject { public function getDictionary() { return array( - 'y' => $this->valueYear, - 'm' => $this->valueMonth, - 'd' => $this->valueDay, + 'd' => $this->valueDate, 't' => $this->valueTime, 'e' => $this->valueEnabled, ); @@ -176,9 +152,7 @@ final class AphrontFormDateControlValue extends Phobject { return null; } - $year = $this->valueYear; - $month = $this->valueMonth; - $day = $this->valueDay; + $date = $this->valueDate; $time = $this->valueTime; $zone = $this->getTimezone(); @@ -203,8 +177,8 @@ final class AphrontFormDateControlValue extends Phobject { } try { - $date = new DateTime("{$year}-{$month}-{$day} {$time}", $zone); - $value = $date->format('U'); + $datetime = new DateTime("{$date} {$time}", $zone); + $value = $datetime->format('U'); } catch (Exception $ex) { $value = null; } diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 9bdab3fc4b..1975d6610a 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -90,10 +90,7 @@ final class PHUICalendarDayView extends AphrontView { } $this->events = msort($this->events, 'getEpochStart'); - - if (!$this->events) { - $first_event_hour = $this->getDateTime()->setTime(8, 0, 0); - } + $first_event_hour = $this->getDateTime()->setTime(8, 0, 0); foreach ($this->events as $event) { if ($event->getIsAllDay()) { diff --git a/src/view/phui/calendar/PHUICalendarListView.php b/src/view/phui/calendar/PHUICalendarListView.php index e1f37fa1ae..fa998f11a6 100644 --- a/src/view/phui/calendar/PHUICalendarListView.php +++ b/src/view/phui/calendar/PHUICalendarListView.php @@ -150,8 +150,11 @@ final class PHUICalendarListView extends AphrontTagView { $this->getUser(), $event->getEpochEnd())); + $start_date = $start->getDateTime()->format('m d Y'); + $end_date = $end->getDateTime()->format('m d Y'); + if ($event->getIsAllDay()) { - if ($start->getValueDay() == $end->getValueDay()) { + if ($start_date == $end_date) { $tip = pht('All day'); } else { $tip = pht( @@ -160,9 +163,7 @@ final class PHUICalendarListView extends AphrontTagView { $end->getValueAsFormat('M j, Y')); } } else { - if ($start->getValueDay() == $end->getValueDay() && - $start->getValueMonth() == $end->getValueMonth() && - $start->getValueYear() == $end->getValueYear()) { + if ($start->getValueDate() == $end->getValueDate()) { $tip = pht( '%s - %s', $start->getValueAsFormat('g:i A'), diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js index f2d092faee..c4b335aefd 100644 --- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js +++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js @@ -78,25 +78,23 @@ JX.behavior('fancy-datepicker', function() { var get_inputs = function() { return { - y: JX.DOM.find(root, 'select', 'year-input'), - m: JX.DOM.find(root, 'select', 'month-input'), - d: JX.DOM.find(root, 'select', 'day-input'), + d: JX.DOM.find(root, 'input', 'date-input'), t: JX.DOM.find(root, 'input', 'time-input') }; }; var read_date = function() { var i = get_inputs(); - value_y = +i.y.value; - value_m = +i.m.value; - value_d = +i.d.value; + var date = i.d.value; + var parts = date.split('/'); + value_y = +parts[2]; + value_m = +parts[0]; + value_d = +parts[1]; }; var write_date = function() { var i = get_inputs(); - i.y.value = value_y; - i.m.value = value_m; - i.d.value = value_d; + i.d.value = value_m + '/' + value_d + '/' + value_y; }; var render = function() { @@ -133,9 +131,12 @@ JX.behavior('fancy-datepicker', function() { return JX.$N('td', {meta: {value: value}, className: class_name}, label); }; - // Render the top bar which allows you to pick a month and year. var render_month = function() { + var valid_date = getValidDate(); + var month = valid_date.getMonth(); + var year = valid_date.getYear() + 1900; + var months = [ 'January', 'February', @@ -152,7 +153,7 @@ JX.behavior('fancy-datepicker', function() { var buttons = [ cell('\u25C0', 'm:-1', false, 'lrbutton'), - cell(months[value_m - 1] + ' ' + value_y, null), + cell(months[month] + ' ' + year, null), cell('\u25B6', 'm:1', false, 'lrbutton')]; return JX.$N( @@ -161,9 +162,21 @@ JX.behavior('fancy-datepicker', function() { JX.$N('tr', {}, buttons)); }; + function getValidDate() { + var written_date = new Date(value_y, value_m-1, value_d); + if (isNaN(written_date.getTime())) { + return new Date(); + } else { + return written_date; + } + } + // Render the day-of-week and calendar views. var render_day = function() { + var today = new Date(); + var valid_date = getValidDate(); + var weeks = []; // First, render the weekday names. @@ -179,16 +192,21 @@ JX.behavior('fancy-datepicker', function() { // Render the calendar itself. NOTE: Javascript uses 0-based month indexes // while we use 1-based month indexes, so we have to adjust for that. var days = []; - var start = new Date(value_y, value_m - 1, 1).getDay(); + var start = new Date( + valid_date.getYear() + 1900, + valid_date.getMonth(), + 1).getDay(); + while (start--) { days.push(cell('', null, false, 'day-placeholder')); } - var today = new Date(); - for (ii = 1; ii <= 31; ii++) { - var date = new Date(value_y, value_m - 1, ii); - if (date.getMonth() != (value_m - 1)) { + var date = new Date( + valid_date.getYear() + 1900, + valid_date.getMonth(), + ii); + if (date.getMonth() != (valid_date.getMonth())) { // We've spilled over into the next month, so stop rendering. break; } @@ -206,7 +224,11 @@ JX.behavior('fancy-datepicker', function() { classes.push('weekend'); } - days.push(cell(ii, 'd:'+ii, value_d == ii, classes.join(' '))); + days.push(cell( + ii, + 'd:'+ii, + valid_date.getDate() == ii, + classes.join(' '))); } // Slice the days into weeks. @@ -263,4 +285,11 @@ JX.behavior('fancy-datepicker', function() { render(); }); + JX.Stratcom.listen('click', null, function(e){ + if (e.getNode('phabricator-datepicker')) { + return; + } + onclose(); + }); + }); From 205adbdda17959334d474fe86288bc7bf7756bc9 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 26 May 2015 07:06:12 +1000 Subject: [PATCH 22/43] Remove "arcanist projects" from Releeph Summary: Ref T7604. Remove arcanist projects from #releeph. Test Plan: I don't really know how to use Releeph but I clicked around and nothing seemed too broken. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D12898 --- .../patches/20150521.releephrepository.sql | 2 + src/__phutil_library_map__.php | 2 - .../ReleephProjectInfoConduitAPIMethod.php | 100 ------------------ .../ReleephBranchNamePreviewController.php | 4 +- .../ReleephProductCreateController.php | 80 ++++---------- .../product/ReleephProductEditController.php | 14 +-- ...ephRequestDifferentialCreateController.php | 11 +- .../releeph/query/ReleephProductQuery.php | 28 ----- .../query/ReleephProductSearchEngine.php | 8 +- .../view/branch/ReleephBranchPreviewView.php | 8 +- .../view/branch/ReleephBranchTemplate.php | 21 +--- 11 files changed, 48 insertions(+), 230 deletions(-) create mode 100644 resources/sql/patches/20150521.releephrepository.sql delete mode 100644 src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php diff --git a/resources/sql/patches/20150521.releephrepository.sql b/resources/sql/patches/20150521.releephrepository.sql new file mode 100644 index 0000000000..6626cb8730 --- /dev/null +++ b/resources/sql/patches/20150521.releephrepository.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_releeph.releeph_project + MODIFY arcanistProjectID int(10) unsigned NULL; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9373d989fe..919a7733ad 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -3151,7 +3151,6 @@ phutil_register_library_map(array( 'ReleephProductTransactionQuery' => 'applications/releeph/query/ReleephProductTransactionQuery.php', 'ReleephProductViewController' => 'applications/releeph/controller/product/ReleephProductViewController.php', 'ReleephProject' => 'applications/releeph/storage/ReleephProject.php', - 'ReleephProjectInfoConduitAPIMethod' => 'applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php', 'ReleephQueryBranchesConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryBranchesConduitAPIMethod.php', 'ReleephQueryProductsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryProductsConduitAPIMethod.php', 'ReleephQueryRequestsConduitAPIMethod' => 'applications/releeph/conduit/ReleephQueryRequestsConduitAPIMethod.php', @@ -6734,7 +6733,6 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', 'PhabricatorPolicyInterface', ), - 'ReleephProjectInfoConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryBranchesConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryProductsConduitAPIMethod' => 'ReleephConduitAPIMethod', 'ReleephQueryRequestsConduitAPIMethod' => 'ReleephConduitAPIMethod', diff --git a/src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php b/src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php deleted file mode 100644 index 0dc45391cd..0000000000 --- a/src/applications/releeph/conduit/ReleephProjectInfoConduitAPIMethod.php +++ /dev/null @@ -1,100 +0,0 @@ - 'optional string', - ); - } - - protected function defineReturnType() { - return 'dict'; - } - - protected function defineErrorTypes() { - return array( - 'ERR_UNKNOWN_ARC' => pht( - "The given Arcanist project name doesn't exist in the ". - "installation of Phabricator you are accessing."), - ); - } - - protected function execute(ConduitAPIRequest $request) { - $arc_project_name = $request->getValue('arcProjectName'); - if ($arc_project_name) { - $arc_project = id(new PhabricatorRepositoryArcanistProject()) - ->loadOneWhere('name = %s', $arc_project_name); - if (!$arc_project) { - throw id(new ConduitException('ERR_UNKNOWN_ARC')) - ->setErrorDescription( - pht( - "Unknown Arcanist project '%s': ". - "are you using the correct Conduit URI?", - $arc_project_name)); - } - - $releeph_projects = id(new ReleephProject()) - ->loadAllWhere('arcanistProjectID = %d', $arc_project->getID()); - } else { - $releeph_projects = id(new ReleephProject())->loadAll(); - } - - $releeph_projects = mfilter($releeph_projects, 'getIsActive'); - - $result = array(); - foreach ($releeph_projects as $releeph_project) { - $selector = $releeph_project->getReleephFieldSelector(); - $fields = $selector->getFieldSpecifications(); - - $fields_info = array(); - foreach ($fields as $field) { - $field->setReleephProject($releeph_project); - if ($field->isEditable()) { - $key = $field->getKeyForConduit(); - $fields_info[$key] = array( - 'class' => get_class($field), - 'name' => $field->getName(), - 'key' => $key, - 'arcHelp' => $field->renderHelpForArcanist(), - ); - } - } - - $releeph_branches = mfilter( - id(new ReleephBranch()) - ->loadAllWhere('releephProjectID = %d', $releeph_project->getID()), - 'getIsActive'); - - $releeph_branches_struct = array(); - foreach ($releeph_branches as $branch) { - $releeph_branches_struct[] = array( - 'branchName' => $branch->getName(), - 'projectName' => $releeph_project->getName(), - 'projectPHID' => $releeph_project->getPHID(), - 'branchPHID' => $branch->getPHID(), - ); - } - - $result[] = array( - 'projectName' => $releeph_project->getName(), - 'projectPHID' => $releeph_project->getPHID(), - 'branches' => $releeph_branches_struct, - 'fields' => $fields_info, - ); - } - - return $result; - } - -} diff --git a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php b/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php index 6eefc0dc83..bf429f9034 100644 --- a/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php +++ b/src/applications/releeph/controller/branch/ReleephBranchNamePreviewController.php @@ -13,10 +13,10 @@ final class ReleephBranchNamePreviewController $template = ReleephBranchTemplate::getDefaultTemplate(); } - $arc_project_id = $request->getInt('arcProjectID'); + $repository_phid = $request->getInt('repositoryPHID'); $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor( - $arc_project_id, + $repository_phid, $request->getUser()); list($name, $errors) = id(new ReleephBranchTemplate()) diff --git a/src/applications/releeph/controller/product/ReleephProductCreateController.php b/src/applications/releeph/controller/product/ReleephProductCreateController.php index 9412f292ed..6d5f29ff37 100644 --- a/src/applications/releeph/controller/product/ReleephProductCreateController.php +++ b/src/applications/releeph/controller/product/ReleephProductCreateController.php @@ -6,9 +6,7 @@ final class ReleephProductCreateController extends ReleephProductController { $request = $this->getRequest(); $name = trim($request->getStr('name')); $trunk_branch = trim($request->getStr('trunkBranch')); - $arc_pr_id = $request->getInt('arcPrID'); - - $arc_projects = $this->loadArcProjects(); + $repository_phid = $request->getStr('repositoryPHID'); $e_name = true; $e_trunk_branch = true; @@ -27,14 +25,10 @@ final class ReleephProductCreateController extends ReleephProductController { 'You must specify which branch you will be picking from.'); } - $arc_project = $arc_projects[$arc_pr_id]; - $pr_repository = null; - if ($arc_project->getRepositoryID()) { - $pr_repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($request->getUser()) - ->withIDs(array($arc_project->getRepositoryID())) - ->executeOne(); - } + $pr_repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($request->getUser()) + ->withPHIDs(array($repository_phid)) + ->executeOne(); if (!$errors) { @@ -42,7 +36,6 @@ final class ReleephProductCreateController extends ReleephProductController { ->setName($name) ->setTrunkBranch($trunk_branch) ->setRepositoryPHID($pr_repository->getPHID()) - ->setArcanistProjectID($arc_project->getID()) ->setCreatedByUserPHID($request->getUser()->getPHID()) ->setIsActive(1); @@ -58,7 +51,7 @@ final class ReleephProductCreateController extends ReleephProductController { } } - $arc_project_options = $this->getArcProjectSelectOptions($arc_projects); + $repo_options = $this->getRepositorySelectOptions(); $product_name_input = id(new AphrontFormTextControl()) ->setLabel(pht('Name')) @@ -68,32 +61,23 @@ final class ReleephProductCreateController extends ReleephProductController { ->setError($e_name) ->setCaption(pht('A name like "Thrift" but not "Thrift releases".')); - $arc_project_input = id(new AphrontFormSelectControl()) - ->setLabel(pht('Arc Project')) - ->setName('arcPrID') - ->setValue($arc_pr_id) - ->setCaption(pht( - "If your Arc project isn't listed, associate it with a repository %s.", - phutil_tag( - 'a', - array( - 'href' => '/repository/', - 'target' => '_blank', - ), - 'here'))) - ->setOptions($arc_project_options); + $repository_input = id(new AphrontFormSelectControl()) + ->setLabel(pht('Repository')) + ->setName('repositoryPHID') + ->setValue($repository_phid) + ->setOptions($repo_options); $branch_name_preview = id(new ReleephBranchPreviewView()) ->setLabel(pht('Example Branch')) ->addControl('projectName', $product_name_input) - ->addControl('arcProjectID', $arc_project_input) + ->addControl('repositoryPHID', $repository_input) ->addStatic('template', '') ->addStatic('isSymbolic', false); $form = id(new AphrontFormView()) ->setUser($request->getUser()) ->appendChild($product_name_input) - ->appendChild($arc_project_input) + ->appendChild($repository_input) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Trunk')) @@ -126,43 +110,23 @@ final class ReleephProductCreateController extends ReleephProductController { )); } - private function loadArcProjects() { - $viewer = $this->getRequest()->getUser(); - - $projects = id(new PhabricatorRepositoryArcanistProjectQuery()) - ->setViewer($viewer) - ->needRepositories(true) + private function getRepositorySelectOptions() { + $repos = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getRequest()->getUser()) ->execute(); - $projects = mfilter($projects, 'getRepository'); - $projects = msort($projects, 'getName'); - - return $projects; - } - - private function getArcProjectSelectOptions(array $arc_projects) { - assert_instances_of($arc_projects, 'PhabricatorRepositoryArcanistProject'); - - $repos = mpull($arc_projects, 'getRepository'); + $repos = msort($repos, 'getName'); $repos = mpull($repos, null, 'getID'); - $groups = array(); - foreach ($arc_projects as $arc_project) { - $id = $arc_project->getID(); - $repo_id = $arc_project->getRepository()->getID(); - $groups[$repo_id][$id] = $arc_project->getName(); - } - $choices = array(); - foreach ($groups as $repo_id => $group) { - $repo_name = $repos[$repo_id]->getName(); - $callsign = $repos[$repo_id]->getCallsign(); - $name = "r{$callsign} ({$repo_name})"; - $choices[$name] = $group; + + foreach ($repos as $repo_id => $repo) { + $repo_name = $repo->getName(); + $callsign = $repo->getCallsign(); + $choices[$repo->getPHID()] = "r{$callsign} ({$repo_name})"; } ksort($choices); - return $choices; } diff --git a/src/applications/releeph/controller/product/ReleephProductEditController.php b/src/applications/releeph/controller/product/ReleephProductEditController.php index 41b405030d..d0769c7b28 100644 --- a/src/applications/releeph/controller/product/ReleephProductEditController.php +++ b/src/applications/releeph/controller/product/ReleephProductEditController.php @@ -15,7 +15,6 @@ final class ReleephProductEditController extends ReleephProductController { $product = id(new ReleephProductQuery()) ->setViewer($viewer) ->withIDs(array($this->productID)) - ->needArcanistProjects(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, @@ -48,7 +47,7 @@ final class ReleephProductEditController extends ReleephProductController { $test_paths = $product->getDetail('testPaths', array()); } - $arc_project_id = $product->getArcanistProjectID(); + $repository_phid = $product->getRepositoryPHID(); if ($request->isFormPost()) { $pusher_phids = $request->getArr('pushers'); @@ -92,8 +91,9 @@ final class ReleephProductEditController extends ReleephProductController { ->setDetail('branchTemplate', $branch_template) ->setDetail('testPaths', $test_paths); - $fake_commit_handle = - ReleephBranchTemplate::getFakeCommitHandleFor($arc_project_id, $viewer); + $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor( + $repository_phid, + $viewer); if ($branch_template) { list($branch_name, $template_errors) = id(new ReleephBranchTemplate()) @@ -136,9 +136,9 @@ final class ReleephProductEditController extends ReleephProductController { $product->getRepository()->getName())) ->appendChild( id(new AphrontFormStaticControl()) - ->setLabel(pht('Arc Project')) + ->setLabel(pht('Repository')) ->setValue( - $product->getArcanistProject()->getName())) + $product->getRepository()->getName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Releeph Project PHID')) @@ -179,7 +179,7 @@ final class ReleephProductEditController extends ReleephProductController { $branch_template_preview = id(new ReleephBranchPreviewView()) ->setLabel(pht('Preview')) ->addControl('template', $branch_template_input) - ->addStatic('arcProjectID', $arc_project_id) + ->addStatic('repositoryPHID', $repository_phid) ->addStatic('isSymbolic', false) ->addStatic('projectName', $product->getName()); diff --git a/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php b/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php index e938fda6c3..3c2dc3d735 100644 --- a/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php +++ b/src/applications/releeph/controller/request/ReleephRequestDifferentialCreateController.php @@ -25,19 +25,18 @@ final class ReleephRequestDifferentialCreateController } $this->revision = $diff_rev; - $arc_project = id(new PhabricatorRepositoryArcanistProject()) - ->loadOneWhere('phid = %s', $this->revision->getArcanistProjectPHID()); + $repository = $this->revision->getRepository(); $projects = id(new ReleephProject())->loadAllWhere( - 'arcanistProjectID = %d AND isActive = 1', - $arc_project->getID()); + 'repositoryPHID = %s AND isActive = 1', + $repository->getPHID()); if (!$projects) { throw new Exception( pht( - "%s belongs to the '%s' Arcanist project, ". + "%s belongs to the '%s' repository, ". "which is not part of any Releeph project!", 'D'.$this->revision->getID(), - $arc_project->getName())); + $repository->getMonogram())); } $branches = id(new ReleephBranch())->loadAllWhere( diff --git a/src/applications/releeph/query/ReleephProductQuery.php b/src/applications/releeph/query/ReleephProductQuery.php index 6fe2b90db3..acfc39c1c2 100644 --- a/src/applications/releeph/query/ReleephProductQuery.php +++ b/src/applications/releeph/query/ReleephProductQuery.php @@ -8,8 +8,6 @@ final class ReleephProductQuery private $phids; private $repositoryPHIDs; - private $needArcanistProjects; - const ORDER_ID = 'order-id'; const ORDER_NAME = 'order-name'; @@ -47,11 +45,6 @@ final class ReleephProductQuery return $this; } - public function needArcanistProjects($need) { - $this->needArcanistProjects = $need; - return $this; - } - protected function loadPage() { $table = new ReleephProject(); $conn_r = $table->establishConnection('r'); @@ -90,27 +83,6 @@ final class ReleephProductQuery return $projects; } - protected function didFilterPage(array $products) { - if ($this->needArcanistProjects) { - $project_ids = array_filter(mpull($products, 'getArcanistProjectID')); - if ($project_ids) { - $projects = id(new PhabricatorRepositoryArcanistProject()) - ->loadAllWhere('id IN (%Ld)', $project_ids); - $projects = mpull($projects, null, 'getID'); - } else { - $projects = array(); - } - - foreach ($products as $product) { - $project_id = $product->getArcanistProjectID(); - $project = idx($projects, $project_id); - $product->attachArcanistProject($project); - } - } - - return $products; - } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); diff --git a/src/applications/releeph/query/ReleephProductSearchEngine.php b/src/applications/releeph/query/ReleephProductSearchEngine.php index 37aecea858..d105568bd3 100644 --- a/src/applications/releeph/query/ReleephProductSearchEngine.php +++ b/src/applications/releeph/query/ReleephProductSearchEngine.php @@ -21,8 +21,7 @@ final class ReleephProductSearchEngine public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new ReleephProductQuery()) - ->setOrder(ReleephProductQuery::ORDER_NAME) - ->needArcanistProjects(true); + ->setOrder(ReleephProductQuery::ORDER_NAME); $active = $saved->getParameter('active'); $value = idx($this->getActiveValues(), $active); @@ -119,11 +118,6 @@ final class ReleephProductSearchEngine ), 'r'.$repo->getCallsign())); - $arc = $product->getArcanistProject(); - if ($arc) { - $item->addAttribute($arc->getName()); - } - $list->addItem($item); } diff --git a/src/applications/releeph/view/branch/ReleephBranchPreviewView.php b/src/applications/releeph/view/branch/ReleephBranchPreviewView.php index afb3dffd4c..462ed73db3 100644 --- a/src/applications/releeph/view/branch/ReleephBranchPreviewView.php +++ b/src/applications/releeph/view/branch/ReleephBranchPreviewView.php @@ -24,7 +24,7 @@ final class ReleephBranchPreviewView extends AphrontFormControl { protected function renderInput() { static $required_params = array( - 'arcProjectID', + 'repositoryPHID', 'projectName', 'isSymbolic', 'template', @@ -43,9 +43,9 @@ final class ReleephBranchPreviewView extends AphrontFormControl { $output_id = celerity_generate_unique_node_id(); Javelin::initBehavior('releeph-preview-branch', array( - 'uri' => '/releeph/branch/preview/', - 'outputID' => $output_id, - 'params' => array( + 'uri' => '/releeph/branch/preview/', + 'outputID' => $output_id, + 'params' => array( 'static' => $this->statics, 'dynamic' => $this->dynamics, ), diff --git a/src/applications/releeph/view/branch/ReleephBranchTemplate.php b/src/applications/releeph/view/branch/ReleephBranchTemplate.php index 79d19b8589..7efaaad9fb 100644 --- a/src/applications/releeph/view/branch/ReleephBranchTemplate.php +++ b/src/applications/releeph/view/branch/ReleephBranchTemplate.php @@ -20,25 +20,14 @@ final class ReleephBranchTemplate { } public static function getFakeCommitHandleFor( - $arc_project_id, + $repository_phid, PhabricatorUser $viewer) { - $arc_project = id(new PhabricatorRepositoryArcanistProject()) - ->load($arc_project_id); - if (!$arc_project) { - throw new Exception( - pht( - "No Arc project found with id '%s'!", - $arc_project_id)); - } + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withPHIDs(array($repository_phid)) + ->executeOne(); - $repository = null; - if ($arc_project->getRepositoryID()) { - $repository = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->withIDs(array($arc_project->getRepositoryID())) - ->executeOne(); - } $fake_handle = 'SOFAKE'; if ($repository) { $fake_handle = id(new PhabricatorObjectHandle()) From 4fc74a67582c3b75fb4b2096965b8293384877bf Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 26 May 2015 07:10:10 +1000 Subject: [PATCH 23/43] Remove arcanist project datasource Summary: Ref T7604. This class is no longer used. Test Plan: `grep` Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D13004 --- src/__phutil_library_map__.php | 2 - .../DiffusionArcanistProjectDatasource.php | 39 ------------------- 2 files changed, 41 deletions(-) delete mode 100644 src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 919a7733ad..390c544347 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -461,7 +461,6 @@ phutil_register_library_map(array( 'DifferentialUpdateRevisionConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateRevisionConduitAPIMethod.php', 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'applications/differential/conduit/DifferentialUpdateUnitResultsConduitAPIMethod.php', 'DifferentialViewPolicyField' => 'applications/differential/customfield/DifferentialViewPolicyField.php', - 'DiffusionArcanistProjectDatasource' => 'applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php', 'DiffusionAuditorDatasource' => 'applications/diffusion/typeahead/DiffusionAuditorDatasource.php', 'DiffusionBranchQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBranchQueryConduitAPIMethod.php', 'DiffusionBranchTableController' => 'applications/diffusion/controller/DiffusionBranchTableController.php', @@ -3710,7 +3709,6 @@ phutil_register_library_map(array( 'DifferentialUpdateRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialUpdateUnitResultsConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialViewPolicyField' => 'DifferentialCoreCustomField', - 'DiffusionArcanistProjectDatasource' => 'PhabricatorTypeaheadDatasource', 'DiffusionAuditorDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'DiffusionBranchQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionBranchTableController' => 'DiffusionController', diff --git a/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php b/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php deleted file mode 100644 index 4f58acfa59..0000000000 --- a/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php +++ /dev/null @@ -1,39 +0,0 @@ -getViewer(); - $raw_query = $this->getRawQuery(); - - $results = array(); - - $arcprojs = id(new PhabricatorRepositoryArcanistProject())->loadAll(); - foreach ($arcprojs as $proj) { - $results[] = id(new PhabricatorTypeaheadResult()) - ->setName($proj->getName()) - ->setPHID($proj->getPHID()); - } - - return $results; - } - -} From 52767e54dae233962942c0b2ef6d5d84e8e539d9 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 26 May 2015 07:10:36 +1000 Subject: [PATCH 24/43] Remove arcanist projects from documentation Summary: Ref T7604. Remove references to "arcanist projects" from documentation. Test Plan: N/A Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D13005 --- src/docs/user/userguide/arcanist_new_project.diviner | 4 ---- src/docs/user/userguide/arcanist_quick_start.diviner | 3 --- src/docs/user/userguide/libraries.diviner | 3 +-- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/docs/user/userguide/arcanist_new_project.diviner b/src/docs/user/userguide/arcanist_new_project.diviner index fbb2ad4a45..448d3ea947 100644 --- a/src/docs/user/userguide/arcanist_new_project.diviner +++ b/src/docs/user/userguide/arcanist_new_project.diviner @@ -49,10 +49,6 @@ Other options include: See below for details about path resolution, or see @{article:libphutil Libraries User Guide} for a general introduction to libphutil libraries. - - **project.name**: name an "Arcanist Project" to associate this working - copy (Git, Mercurial) or directory (SVN) with. Previously, this was a - required option, but `arc` can now usually operate without it in Git and - Mercurial. This option was previously called `project_id`. - **https.cabundle**: specifies the path to an alternate certificate bundle for use when making HTTPS connections. - **lint.engine**: the name of a subclass of diff --git a/src/docs/user/userguide/arcanist_quick_start.diviner b/src/docs/user/userguide/arcanist_quick_start.diviner index 261056bbeb..98633bfaef 100644 --- a/src/docs/user/userguide/arcanist_quick_start.diviner +++ b/src/docs/user/userguide/arcanist_quick_start.diviner @@ -44,12 +44,9 @@ Create a `.arcconfig` file in your project's working copy: yourproject/ $ $EDITOR .arcconfig yourproject/ $ cat .arcconfig { - "project.name" : "yourprojectname", "phabricator.uri" : "https://phabricator.example.com/" } -Set `project.name` to a string that identifies the project. - Set `phabricator.uri` to the URI for your Phabricator install (where `arc` should send changes to). diff --git a/src/docs/user/userguide/libraries.diviner b/src/docs/user/userguide/libraries.diviner index 33f6f57b67..656b167d8d 100644 --- a/src/docs/user/userguide/libraries.diviner +++ b/src/docs/user/userguide/libraries.diviner @@ -83,8 +83,7 @@ Phabricator. For example, you might write this file to `libcustom/.arcconfig`: { - "project.name" : "libcustom", - "load" : [ + "load": [ "phabricator/src/" ] } From 6e5e6a1a8c31540c32b20ef9c70e228bdc673e30 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 26 May 2015 07:11:46 +1000 Subject: [PATCH 25/43] Remove "arcanist project" controllers Summary: Ref T7603. Ref T7604. There is no need for these controllers anymore as the "Arcanist Project" data is not used anywhere. Depends on D12894 and D12898. Test Plan: Went to `/repository/project/edit/2/` and got a 404. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T7603, T7604 Differential Revision: https://secure.phabricator.com/D12897 --- src/__phutil_library_map__.php | 4 - .../PhabricatorRepositoriesApplication.php | 4 - ...ositoryArcanistProjectDeleteController.php | 42 --------- ...epositoryArcanistProjectEditController.php | 85 ------------------- .../PhabricatorRepositoryListController.php | 63 -------------- 5 files changed, 198 deletions(-) delete mode 100644 src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php delete mode 100644 src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 390c544347..d1c7215cae 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2387,8 +2387,6 @@ phutil_register_library_map(array( 'PhabricatorRepositoriesSetupCheck' => 'applications/config/check/PhabricatorRepositoriesSetupCheck.php', 'PhabricatorRepository' => 'applications/repository/storage/PhabricatorRepository.php', 'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/PhabricatorRepositoryArcanistProject.php', - 'PhabricatorRepositoryArcanistProjectDeleteController' => 'applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php', - 'PhabricatorRepositoryArcanistProjectEditController' => 'applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php', 'PhabricatorRepositoryArcanistProjectPHIDType' => 'applications/repository/phid/PhabricatorRepositoryArcanistProjectPHIDType.php', 'PhabricatorRepositoryArcanistProjectQuery' => 'applications/repository/query/PhabricatorRepositoryArcanistProjectQuery.php', 'PhabricatorRepositoryAuditRequest' => 'applications/repository/storage/PhabricatorRepositoryAuditRequest.php', @@ -5832,8 +5830,6 @@ phutil_register_library_map(array( 'PhabricatorPolicyInterface', 'PhabricatorDestructibleInterface', ), - 'PhabricatorRepositoryArcanistProjectDeleteController' => 'PhabricatorRepositoryController', - 'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController', 'PhabricatorRepositoryArcanistProjectPHIDType' => 'PhabricatorPHIDType', 'PhabricatorRepositoryArcanistProjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryAuditRequest' => array( diff --git a/src/applications/repository/application/PhabricatorRepositoriesApplication.php b/src/applications/repository/application/PhabricatorRepositoriesApplication.php index 534db063e7..f740d05dcd 100644 --- a/src/applications/repository/application/PhabricatorRepositoriesApplication.php +++ b/src/applications/repository/application/PhabricatorRepositoriesApplication.php @@ -30,10 +30,6 @@ final class PhabricatorRepositoriesApplication extends PhabricatorApplication { return array( '/repository/' => array( '' => 'PhabricatorRepositoryListController', - 'project/edit/(?P[1-9]\d*)/' - => 'PhabricatorRepositoryArcanistProjectEditController', - 'project/delete/(?P[1-9]\d*)/' - => 'PhabricatorRepositoryArcanistProjectDeleteController', ), ); } diff --git a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php deleted file mode 100644 index 04c2008897..0000000000 --- a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectDeleteController.php +++ /dev/null @@ -1,42 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - - $arc_project = - id(new PhabricatorRepositoryArcanistProject())->load($this->id); - if (!$arc_project) { - return new Aphront404Response(); - } - - $request = $this->getRequest(); - - if ($request->isDialogFormPost()) { - $arc_project->delete(); - return id(new AphrontRedirectResponse())->setURI('/repository/'); - } - - $dialog = new AphrontDialogView(); - $dialog - ->setUser($request->getUser()) - ->setTitle(pht('Really delete this arcanist project?')) - ->appendChild( - pht( - 'Really delete the "%s" arcanist project? '. - 'This operation can not be undone.', - $arc_project->getName())) - ->setSubmitURI('/repository/project/delete/'.$this->id.'/') - ->addSubmitButton(pht('Delete Arcanist Project')) - ->addCancelButton('/repository/'); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } -} diff --git a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php deleted file mode 100644 index c453ebb057..0000000000 --- a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php +++ /dev/null @@ -1,85 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - - $request = $this->getRequest(); - $user = $request->getUser(); - - $project = id(new PhabricatorRepositoryArcanistProject())->load($this->id); - if (!$project) { - return new Aphront404Response(); - } - - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->execute(); - $repos = array( - 0 => 'None', - ); - foreach ($repositories as $repository) { - $callsign = $repository->getCallsign(); - $name = $repository->getname(); - $repos[$repository->getID()] = "r{$callsign} ({$name})"; - } - // note "None" will still be first thanks to 'r' prefix - asort($repos); - - if ($request->isFormPost()) { - $repo_id = $request->getInt('repository', 0); - if (isset($repos[$repo_id])) { - $project->setRepositoryID($repo_id); - $project->save(); - - return id(new AphrontRedirectResponse()) - ->setURI('/repository/'); - } - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel(pht('Name')) - ->setValue($project->getName())) - ->appendChild( - id(new AphrontFormStaticControl()) - ->setLabel('PHID') - ->setValue($project->getPHID())) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Repository')) - ->setOptions($repos) - ->setName('repository') - ->setValue($project->getRepositoryID())) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->addCancelButton('/repository/') - ->setValue(pht('Save'))); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText(pht('Edit Arcanist Project')); - $panel->setForm($form); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb(pht('Edit Project')); - - return $this->buildApplicationPage( - array( - $crumbs, - $panel, - ), - array( - 'title' => pht('Edit Project'), - )); - } - -} diff --git a/src/applications/repository/controller/PhabricatorRepositoryListController.php b/src/applications/repository/controller/PhabricatorRepositoryListController.php index 89d068e283..202dd5977f 100644 --- a/src/applications/repository/controller/PhabricatorRepositoryListController.php +++ b/src/applications/repository/controller/PhabricatorRepositoryListController.php @@ -85,68 +85,6 @@ final class PhabricatorRepositoryListController $panel->setHeader($header); $panel->appendChild($table); - $projects = id(new PhabricatorRepositoryArcanistProject())->loadAll(); - - $rows = array(); - foreach ($projects as $project) { - $repo = idx($repos, $project->getRepositoryID()); - if ($repo) { - $repo_name = $repo->getName(); - } else { - $repo_name = '-'; - } - - $rows[] = array( - $project->getName(), - $repo_name, - phutil_tag( - 'a', - array( - 'href' => '/repository/project/edit/'.$project->getID().'/', - 'class' => 'button grey small', - ), - pht('Edit')), - javelin_tag( - 'a', - array( - 'href' => '/repository/project/delete/'.$project->getID().'/', - 'class' => 'button grey small', - 'sigil' => 'workflow', - ), - pht('Delete')), - ); - - } - - $project_table = new AphrontTableView($rows); - $project_table->setNoDataString(pht('No Arcanist Projects')); - $project_table->setHeaders( - array( - pht('Project ID'), - pht('Repository'), - '', - '', - )); - $project_table->setColumnClasses( - array( - '', - 'wide', - 'action', - 'action', - )); - - $project_table->setColumnVisibility( - array( - true, - true, - $is_admin, - $is_admin, - )); - - $project_panel = new PHUIObjectBoxView(); - $project_panel->setHeaderText(pht('Arcanist Projects')); - $project_panel->appendChild($project_table); - $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Repository List')); @@ -154,7 +92,6 @@ final class PhabricatorRepositoryListController array( $crumbs, $panel, - $project_panel, ), array( 'title' => pht('Repository List'), From 04bf04d783db9019c746d7c787bdace8660afe62 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Mon, 25 May 2015 20:51:38 -0700 Subject: [PATCH 26/43] Fix day view start hour Summary: Ref T8308, Fix day view start hour Test Plan: Day views start at 8am OR hour of first event start time if there is an event scheduled before 8am on that day OR midnight if there is a non-all-day multi-day event carried over from the previous day Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8308 Differential Revision: https://secure.phabricator.com/D13013 --- .../phui/calendar/PHUICalendarDayView.php | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/view/phui/calendar/PHUICalendarDayView.php b/src/view/phui/calendar/PHUICalendarDayView.php index 1975d6610a..844cd31222 100644 --- a/src/view/phui/calendar/PHUICalendarDayView.php +++ b/src/view/phui/calendar/PHUICalendarDayView.php @@ -91,6 +91,7 @@ final class PHUICalendarDayView extends AphrontView { $this->events = msort($this->events, 'getEpochStart'); $first_event_hour = $this->getDateTime()->setTime(8, 0, 0); + $midnight = $this->getDateTime()->setTime(0, 0, 0); foreach ($this->events as $event) { if ($event->getIsAllDay()) { @@ -99,21 +100,17 @@ final class PHUICalendarDayView extends AphrontView { if ($event->getEpochStart() <= $day_end_epoch && $event->getEpochEnd() > $day_start_epoch) { - if ($first_event_hour === null) { + if ($event->getEpochStart() < $midnight->format('U') && + $event->getEpochEnd() > $midnight->format('U')) { + $first_event_hour = clone $midnight; + } + + if ($event->getEpochStart() < $first_event_hour->format('U') && + $event->getEpochStart() > $midnight->format('U')) { $first_event_hour = PhabricatorTime::getDateTimeFromEpoch( $event->getEpochStart(), $viewer); - - $midnight = $this->getDateTime()->setTime(0, 0, 0); - - if ($first_event_hour->format('U') < $midnight->format('U')) { - $first_event_hour = clone $midnight; - } - - $eight_am = $this->getDateTime()->setTime(8, 0, 0); - if ($eight_am->format('U') < $first_event_hour->format('U')) { - $first_event_hour = clone $eight_am; - } + $first_event_hour->setTime($first_event_hour->format('h'), 0, 0); } $event_start = max($event->getEpochStart(), $day_start_epoch); From dcfc381a0afdc87189fefd727c36d1733e20d9a9 Mon Sep 17 00:00:00 2001 From: Joshua Spence Date: Tue, 26 May 2015 23:14:46 +1000 Subject: [PATCH 27/43] Fix a broken migration Summary: Ref T7604. This migration doesn't actually work because it is in the wrong directory. Test Plan: Ran `./bin/storage upgrade` and saw the migration applied. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T7604 Differential Revision: https://secure.phabricator.com/D13014 --- .../sql/{patches => autopatches}/20150521.releephrepository.sql | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename resources/sql/{patches => autopatches}/20150521.releephrepository.sql (100%) diff --git a/resources/sql/patches/20150521.releephrepository.sql b/resources/sql/autopatches/20150521.releephrepository.sql similarity index 100% rename from resources/sql/patches/20150521.releephrepository.sql rename to resources/sql/autopatches/20150521.releephrepository.sql From 7ab7c713191612e1da146bab5558dfbd46eb00c6 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 26 May 2015 06:53:48 -0700 Subject: [PATCH 28/43] Mark arcanistProjectID as nullable Reviewers: joshuaspence --- src/applications/releeph/storage/ReleephProject.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/applications/releeph/storage/ReleephProject.php b/src/applications/releeph/storage/ReleephProject.php index ae34894658..4dd91b38ab 100644 --- a/src/applications/releeph/storage/ReleephProject.php +++ b/src/applications/releeph/storage/ReleephProject.php @@ -35,6 +35,7 @@ final class ReleephProject extends ReleephDAO 'name' => 'text128', 'trunkBranch' => 'text255', 'isActive' => 'bool', + 'arcanistProjectID' => 'id?', ), self::CONFIG_KEY_SCHEMA => array( 'projectName' => array( From c2351208c0e522ea76ded2050777872d4026436f Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 26 May 2015 09:55:59 -0700 Subject: [PATCH 29/43] String inputs should be validated and replaced by "today" values. Summary: Ref T8060, String inputs should be validated and replaced by "today" values. Test Plan: Open Calendar advanced search, enter "tea time" in date input control, open datepicker, "today" should be selected. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: Korvin, epriestley Maniphest Tasks: T8060 Differential Revision: https://secure.phabricator.com/D13018 --- webroot/rsrc/js/core/behavior-fancy-datepicker.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js index c4b335aefd..d00f6cc77f 100644 --- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js +++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js @@ -64,7 +64,9 @@ JX.behavior('fancy-datepicker', function() { JX.DOM.remove(picker); picker = null; JX.DOM.alterClass(root, 'picker-open', false); - e.kill(); + if (e) { + e.kill(); + } root = null; }; @@ -167,6 +169,11 @@ JX.behavior('fancy-datepicker', function() { if (isNaN(written_date.getTime())) { return new Date(); } else { + //year 01 should be 2001, not 1901 + if (written_date.getYear() < 70) { + value_y += 2000; + written_date = new Date(value_y, value_m-1, value_d); + } return written_date; } } @@ -254,6 +261,11 @@ JX.behavior('fancy-datepicker', function() { return; } + var valid_date = getValidDate(); + value_y = valid_date.getYear() + 1900; + value_m = valid_date.getMonth() + 1; + value_d = valid_date.getDate(); + var p = data.value.split(':'); switch (p[0]) { case 'm': From b292c29dc36514dd05166aeb3cdf52f1f1e262d9 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 26 May 2015 11:27:51 -0700 Subject: [PATCH 30/43] Decouple date and time input classes on AphrontFormDateControl's so that disabling time doesn't disable date. Summary: Fixes T8319, Decouple date and time input classes on AphrontFormDateControl's so that disabling time doesn't disable date. Test Plan: Create new event, make it all day, time input should be hidden, but not date input. Reviewers: chad, #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8319 Differential Revision: https://secure.phabricator.com/D13021 --- resources/celerity/map.php | 24 +++++++++---------- .../form/control/AphrontFormDateControl.php | 8 +++---- webroot/rsrc/css/phui/phui-form-view.css | 10 ++++---- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index e093be5ae8..2989303d4e 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '439658b5', + 'core.pkg.css' => '4e7df908', 'core.pkg.js' => '328799d0', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', @@ -134,7 +134,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '94d5dcd8', 'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5', 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', - 'rsrc/css/phui/phui-form-view.css' => '79793450', + 'rsrc/css/phui/phui-form-view.css' => '808329f2', 'rsrc/css/phui/phui-form.css' => 'f535f938', 'rsrc/css/phui/phui-header-view.css' => '75aaf372', 'rsrc/css/phui/phui-icon.css' => 'bc766998', @@ -443,7 +443,7 @@ return array( 'rsrc/js/core/behavior-device.js' => 'a205cf28', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', - 'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f', + 'rsrc/js/core/behavior-fancy-datepicker.js' => '510b5809', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', @@ -576,7 +576,7 @@ return array( 'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => '38dcf3c8', - 'javelin-behavior-fancy-datepicker' => '5c0f680f', + 'javelin-behavior-fancy-datepicker' => '510b5809', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', @@ -776,7 +776,7 @@ return array( 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => 'dd8ddf27', 'phui-form-css' => 'f535f938', - 'phui-form-view-css' => '79793450', + 'phui-form-view-css' => '808329f2', 'phui-header-view-css' => '75aaf372', 'phui-icon-view-css' => 'bc766998', 'phui-image-mask-css' => '5a8b09c8', @@ -1167,6 +1167,13 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), + '510b5809' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -1234,13 +1241,6 @@ return array( 'javelin-uri', 'javelin-routable', ), - '5c0f680f' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/view/form/control/AphrontFormDateControl.php b/src/view/form/control/AphrontFormDateControl.php index ca6d87b26c..ac026d410b 100644 --- a/src/view/form/control/AphrontFormDateControl.php +++ b/src/view/form/control/AphrontFormDateControl.php @@ -184,14 +184,14 @@ final class AphrontFormDateControl extends AphrontFormControl { 'sigil' => 'date-input', 'value' => $this->getDateInputValue(), 'type' => 'text', - 'class' => 'aphront-form-date-time-input', + 'class' => 'aphront-form-date-input', ), ''); $date_div = javelin_tag( 'div', array( - 'class' => 'aphront-form-date-time-input-container', + 'class' => 'aphront-form-date-input-container', ), $date_sel); @@ -225,7 +225,7 @@ final class AphrontFormDateControl extends AphrontFormControl { 'sigil' => 'time-input', 'value' => $this->getTimeInputValue(), 'type' => 'text', - 'class' => 'aphront-form-date-time-input', + 'class' => 'aphront-form-time-input', ), ''); @@ -233,7 +233,7 @@ final class AphrontFormDateControl extends AphrontFormControl { 'div', array( 'id' => $time_id, - 'class' => 'aphront-form-date-time-input-container', + 'class' => 'aphront-form-time-input-container', ), $time_sel); diff --git a/webroot/rsrc/css/phui/phui-form-view.css b/webroot/rsrc/css/phui/phui-form-view.css index 2600de81ac..1cd34f4f22 100644 --- a/webroot/rsrc/css/phui/phui-form-view.css +++ b/webroot/rsrc/css/phui/phui-form-view.css @@ -334,17 +334,19 @@ table.aphront-form-control-checkbox-layout th { font-size: 16px; } -.aphront-form-date-container .aphront-form-date-time-input-container { +.aphront-form-date-container .aphront-form-time-input-container, +.aphront-form-date-container .aphront-form-date-input-container { position: relative; display: inline-block; width: 7em; } -.aphront-form-date-container input.aphront-form-date-time-input { +.aphront-form-date-container input.aphront-form-time-input, +.aphront-form-date-container input.aphront-form-date-input { width: 7em; } -.aphront-form-date-time-input-container div.jx-typeahead-results a.jx-result { +.aphront-form-time-input-container div.jx-typeahead-results a.jx-result { border: none; } @@ -470,7 +472,7 @@ properly, and submit values. */ opacity: 0.5; } -.aphront-form-date-container.no-time .aphront-form-date-time-input{ +.aphront-form-date-container.no-time .aphront-form-time-input{ display: none; } From 08a9e0f22aff28576c18089139fa103faae197e5 Mon Sep 17 00:00:00 2001 From: Aviv Eyal Date: Tue, 26 May 2015 11:22:04 -0700 Subject: [PATCH 31/43] Hide Land to GitHub Summary: We don't want to support this right now, so disable it, similar to "Land To Hosted" feature. Keep to the code as an example for advanced installs. Ref T182, T8313 Test Plan: load a diff. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: epriestley Maniphest Tasks: T182, T8313 Differential Revision: https://secure.phabricator.com/D13022 --- .../landing/DifferentialGitHubLandingStrategy.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php b/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php index c805083cd4..b4ff727cbe 100644 --- a/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php +++ b/src/applications/differential/landing/DifferentialGitHubLandingStrategy.php @@ -47,6 +47,11 @@ final class DifferentialGitHubLandingStrategy DifferentialRevision $revision, PhabricatorRepository $repository) { + // TODO: This temporarily disables this action, because it doesn't work + // and is confusing to users. If you want to use it, comment out this line + // for now and we'll provide real support eventually. + return; + $vcs = $repository->getVersionControlSystem(); if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) { return; From 46c5e055a291923634bc2ec61fe62ca06c610bc4 Mon Sep 17 00:00:00 2001 From: lkassianik Date: Tue, 26 May 2015 14:28:07 -0700 Subject: [PATCH 32/43] All day events should obey selected query range in viewer timezone. Summary: Fixes T8147, All day events should obey selected query range in viewer timezone. Test Plan: Create all day event May 25, query for events May 26-27. All day event should not be part of the query results list. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8147 Differential Revision: https://secure.phabricator.com/D13023 --- .../query/PhabricatorCalendarEventQuery.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php index 474ac49a46..bdc87aaf26 100644 --- a/src/applications/calendar/query/PhabricatorCalendarEventQuery.php +++ b/src/applications/calendar/query/PhabricatorCalendarEventQuery.php @@ -180,6 +180,21 @@ final class PhabricatorCalendarEventQuery protected function willFilterPage(array $events) { + $range_start = $this->rangeBegin; + $range_end = $this->rangeEnd; + + foreach ($events as $key => $event) { + $event_start = $event->getDateFrom(); + $event_end = $event->getDateTo(); + + if ($range_start && $event_end < $range_start) { + unset($events[$key]); + } + if ($range_end && $event_start > $range_end) { + unset($events[$key]); + } + } + $phids = array(); foreach ($events as $event) { @@ -197,6 +212,8 @@ final class PhabricatorCalendarEventQuery $event->attachInvitees($event_invitees); } + $events = msort($events, 'getDateFrom'); + return $events; } From ef731b42b78a0c0446cc481d2a78cc479449c3d8 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:06:55 -0700 Subject: [PATCH 33/43] Turn lint TODO comments back on Summary: These were accidentally disabled here: https://secure.phabricator.com/diffusion/P/change/master/.arclint;36e2d02d6ec5db26f4dfc813a4f02238d18cc8a5 Test Plan: iiam Reviewers: joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D13029 --- .arclint | 1 + 1 file changed, 1 insertion(+) diff --git a/.arclint b/.arclint index e8b804fedb..0077bdf518 100644 --- a/.arclint +++ b/.arclint @@ -74,6 +74,7 @@ "type": "xhpast", "include": "(\\.php$)", "severity": { + "16": "advice", "34": "error" }, "xhpast.blacklisted.function": { From fcac85d80733711b404b56c7c9b8f932f171f4f0 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:28:27 -0700 Subject: [PATCH 34/43] Allow configuration of a "staging area" for each repository Summary: Ref T8238. This allows configuration of a "staging area" for Git repositories, which is the URI to some Git repository (possibly the same repository). If a staging area is configured, `arc` will push a copy of anything it creates a diff for there (see next revision). This primarily makes handoff to build systems easier. This is a bit leaky and I intend for it to eventually be positioned as a less-preferred solution, but from the perspective of build systems it's the same as the real (virtual ref) solution that I want to build. Test Plan: Ran `arc diff` with various flags, saw appropriate changes copied into the staging area. See also discussion in T8238. Reviewers: btrahan Reviewed By: btrahan Subscribers: cburroughs, epriestley Maniphest Tasks: T8238 Differential Revision: https://secure.phabricator.com/D13019 --- resources/celerity/map.php | 24 ++--- src/__phutil_library_map__.php | 2 + .../PhabricatorDiffusionApplication.php | 1 + .../DiffusionRepositoryEditMainController.php | 53 +++++++++++ ...ffusionRepositoryEditStagingController.php | 92 +++++++++++++++++++ .../editor/PhabricatorRepositoryEditor.php | 8 ++ .../storage/PhabricatorRepository.php | 21 +++++ .../PhabricatorRepositoryTransaction.php | 25 ++++- 8 files changed, 212 insertions(+), 14 deletions(-) create mode 100644 src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 2989303d4e..bf3f6f59fa 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '4e7df908', + 'core.pkg.css' => '439658b5', 'core.pkg.js' => '328799d0', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => 'bb338e4b', @@ -134,7 +134,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '94d5dcd8', 'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5', 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', - 'rsrc/css/phui/phui-form-view.css' => '808329f2', + 'rsrc/css/phui/phui-form-view.css' => '79793450', 'rsrc/css/phui/phui-form.css' => 'f535f938', 'rsrc/css/phui/phui-header-view.css' => '75aaf372', 'rsrc/css/phui/phui-icon.css' => 'bc766998', @@ -443,7 +443,7 @@ return array( 'rsrc/js/core/behavior-device.js' => 'a205cf28', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', - 'rsrc/js/core/behavior-fancy-datepicker.js' => '510b5809', + 'rsrc/js/core/behavior-fancy-datepicker.js' => '2d4029a8', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', @@ -576,7 +576,7 @@ return array( 'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => '38dcf3c8', - 'javelin-behavior-fancy-datepicker' => '510b5809', + 'javelin-behavior-fancy-datepicker' => '2d4029a8', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', @@ -776,7 +776,7 @@ return array( 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => 'dd8ddf27', 'phui-form-css' => 'f535f938', - 'phui-form-view-css' => '808329f2', + 'phui-form-view-css' => '79793450', 'phui-header-view-css' => '75aaf372', 'phui-icon-view-css' => 'bc766998', 'phui-image-mask-css' => '5a8b09c8', @@ -1042,6 +1042,13 @@ return array( 'javelin-install', 'javelin-event', ), + '2d4029a8' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '331b1611' => array( 'javelin-install', ), @@ -1167,13 +1174,6 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), - '510b5809' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), '519705ea' => array( 'javelin-install', 'javelin-dom', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index d1c7215cae..6a67775a9a 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -595,6 +595,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php', 'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php', 'DiffusionRepositoryEditMainController' => 'applications/diffusion/controller/DiffusionRepositoryEditMainController.php', + 'DiffusionRepositoryEditStagingController' => 'applications/diffusion/controller/DiffusionRepositoryEditStagingController.php', 'DiffusionRepositoryEditStorageController' => 'applications/diffusion/controller/DiffusionRepositoryEditStorageController.php', 'DiffusionRepositoryEditSubversionController' => 'applications/diffusion/controller/DiffusionRepositoryEditSubversionController.php', 'DiffusionRepositoryEditUpdateController' => 'applications/diffusion/controller/DiffusionRepositoryEditUpdateController.php', @@ -3831,6 +3832,7 @@ phutil_register_library_map(array( 'DiffusionRepositoryEditEncodingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditHostingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditMainController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryEditStagingController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditStorageController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditSubversionController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryEditUpdateController' => 'DiffusionRepositoryEditController', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php index 14a3338f5e..0f44db3d13 100644 --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -101,6 +101,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication { '(?Pserve)/' => 'DiffusionRepositoryEditHostingController', 'update/' => 'DiffusionRepositoryEditUpdateController', 'symbol/' => 'DiffusionRepositorySymbolsController', + 'staging/' => 'DiffusionRepositoryEditStagingController', ), 'pathtree/(?P.*)' => 'DiffusionPathTreeController', 'mirror/' => array( diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 905021ab23..824a7afe1d 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -30,6 +30,7 @@ final class DiffusionRepositoryEditMainController $has_branches = ($is_git || $is_hg); $has_local = $repository->usesLocalWorkingCopy(); + $supports_staging = $repository->supportsStaging(); $crumbs = $this->buildApplicationCrumbs($is_main = true); @@ -92,6 +93,13 @@ final class DiffusionRepositoryEditMainController $this->buildStorageActions($repository)); } + $staging_properties = null; + if ($supports_staging) { + $staging_properties = $this->buildStagingProperties( + $repository, + $this->buildStagingActions($repository)); + } + $actions_properties = $this->buildActionsProperties( $repository, $this->buildActionsActions($repository)); @@ -157,6 +165,12 @@ final class DiffusionRepositoryEditMainController ->addPropertyList($storage_properties); } + if ($staging_properties) { + $boxes[] = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Staging')) + ->addPropertyList($staging_properties); + } + $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) ->addPropertyList($encoding_properties); @@ -609,6 +623,45 @@ final class DiffusionRepositoryEditMainController return $view; } + + private function buildStagingActions(PhabricatorRepository $repository) { + $viewer = $this->getViewer(); + + $view = id(new PhabricatorActionListView()) + ->setObjectURI($this->getRequest()->getRequestURI()) + ->setUser($viewer); + + $edit = id(new PhabricatorActionView()) + ->setIcon('fa-pencil') + ->setName(pht('Edit Staging')) + ->setHref( + $this->getRepositoryControllerURI($repository, 'edit/staging/')); + $view->addAction($edit); + + return $view; + } + + private function buildStagingProperties( + PhabricatorRepository $repository, + PhabricatorActionListView $actions) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer) + ->setActionList($actions); + + $staging_uri = $repository->getStagingURI(); + if (!$staging_uri) { + $staging_uri = phutil_tag('em', array(), pht('No Staging Area')); + } + + $view->addProperty( + pht('Staging Area'), + $staging_uri); + + return $view; + } + private function buildHostingActions(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php new file mode 100644 index 0000000000..9fcfd767c0 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditStagingController.php @@ -0,0 +1,92 @@ +getUser(); + $drequest = $this->diffusionRequest; + $repository = $drequest->getRepository(); + + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer($user) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + )) + ->withIDs(array($repository->getID())) + ->executeOne(); + if (!$repository) { + return new Aphront404Response(); + } + + if (!$repository->supportsStaging()) { + return new Aphront404Response(); + } + + $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); + + $v_area = $repository->getHumanReadableDetail('staging-uri'); + if ($request->isFormPost()) { + $v_area = $request->getStr('area'); + + $xactions = array(); + $template = id(new PhabricatorRepositoryTransaction()); + + $type_encoding = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; + + $xactions[] = id(clone $template) + ->setTransactionType($type_encoding) + ->setNewValue($v_area); + + id(new PhabricatorRepositoryEditor()) + ->setContinueOnNoEffect(true) + ->setContentSourceFromRequest($request) + ->setActor($user) + ->applyTransactions($repository, $xactions); + + return id(new AphrontRedirectResponse())->setURI($edit_uri); + } + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb(pht('Edit Staging')); + + $title = pht('Edit %s', $repository->getName()); + + $form = id(new AphrontFormView()) + ->setUser($user) + ->appendRemarkupInstructions( + pht( + "To make it easier to run integration tests and builds on code ". + "under review, you can configure a **Staging Area**. When `arc` ". + "creates a diff, it will push a copy of the changes to the ". + "configured staging area with a corresponding tag.". + "\n\n". + "IMPORTANT: This feature is new, experimental, and not supported. ". + "Use it at your own risk.")) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Staging Area URI')) + ->setName('area') + ->setValue($v_area)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue(pht('Save')) + ->addCancelButton($edit_uri)); + + $object_box = id(new PHUIObjectBoxView()) + ->setHeaderText($title) + ->setForm($form); + + return $this->buildApplicationPage( + array( + $crumbs, + $object_box, + ), + array( + 'title' => $title, + )); + } + +} diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 69012bdcd2..2e769975ab 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -43,6 +43,7 @@ final class PhabricatorRepositoryEditor $types[] = PhabricatorRepositoryTransaction::TYPE_SERVICE; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE; $types[] = PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES; + $types[] = PhabricatorRepositoryTransaction::TYPE_STAGING_URI; $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; @@ -104,6 +105,8 @@ final class PhabricatorRepositoryEditor return $object->getSymbolLanguages(); case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: return $object->getSymbolSources(); + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: + return $object->getDetail('staging-uri'); } } @@ -139,6 +142,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: return $xaction->getNewValue(); case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: @@ -219,6 +223,9 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: $object->setDetail('symbol-sources', $xaction->getNewValue()); return; + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: + $object->setDetail('staging-uri', $xaction->getNewValue()); + return; case PhabricatorRepositoryTransaction::TYPE_ENCODING: // Make sure the encoding is valid by converting to UTF-8. This tests // that the user has mbstring installed, and also that they didn't type @@ -330,6 +337,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: + case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 14cc11c01d..13dd10a878 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -136,6 +136,11 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO 'isHosted' => $this->isHosted(), 'isImporting' => $this->isImporting(), 'encoding' => $this->getDetail('encoding'), + 'staging' => array( + 'supported' => $this->supportsStaging(), + 'prefix' => 'phabricator', + 'uri' => $this->getStagingURI(), + ), ); } @@ -1797,6 +1802,22 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO } +/* -( Staging )-------------------------------------------------------------*/ + + + public function supportsStaging() { + return $this->isGit(); + } + + + public function getStagingURI() { + if (!$this->supportsStaging()) { + return null; + } + return $this->getDetail('staging-uri', null); + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index 9907cb0a01..de1310f320 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -27,6 +27,7 @@ final class PhabricatorRepositoryTransaction const TYPE_SERVICE = 'repo:service'; const TYPE_SYMBOLS_SOURCES = 'repo:symbol-source'; const TYPE_SYMBOLS_LANGUAGE = 'repo:symbol-language'; + const TYPE_STAGING_URI = 'repo:staging-uri'; // TODO: Clean up these legacy transaction types. const TYPE_SSH_LOGIN = 'repo:ssh-login'; @@ -412,9 +413,29 @@ final class PhabricatorRepositoryTransaction case self::TYPE_SYMBOLS_LANGUAGE: return pht('%s changed indexed languages from %s to %s.', + $this->renderHandleLink($author_phid), + $old ? implode(', ', $old) : pht('Any'), + $new ? implode(', ', $new) : pht('Any')); + + case self::TYPE_STAGING_URI: + if (!$old) { + return pht( + '%s set "%s" as the staging area for this repository.', $this->renderHandleLink($author_phid), - $old ? implode(', ', $old) : pht('Any'), - $new ? implode(', ', $new) : pht('Any')); + $new); + } else if (!$new) { + return pht( + '%s removed "%s" as the staging area for this repository.', + $this->renderHandleLink($author_phid), + $old); + } else { + return pht( + '%s changed the staging area for this repository from '. + '"%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } } return parent::getTitle(); From e9f4a84a892ca0150e22c45356cdd559b26ab8b3 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:28:38 -0700 Subject: [PATCH 35/43] Allow inline comments to be individually hidden Summary: Ref T7447. Implements per-viewer comment hiding. Once a comment is obsolete or uninteresting, you can hide it completely. This is sticky per-user. My hope is that this will strike a better balance between concerns than some of the other approaches (conservative porting, summarization, hide-all). Specifically, this adds a new action here: {F435621} Clicking it completely collapses the comment into a small icon on the previous line, and saves the comment state as hidden for you: {F435626} You can click the icon to reveal all hidden comments below the line. Test Plan: - Hid comments. - Showed comments. - Created, edited, deleted and submitted comments. - Used Diffusion comments (hiding is not implemented there yet, but I'd plan to bring it there eventually if it works out in Differential). Reviewers: btrahan, chad Reviewed By: btrahan Subscribers: jparise, yelirekim, epriestley Maniphest Tasks: T7447 Differential Revision: https://secure.phabricator.com/D13009 --- resources/celerity/map.php | 46 ++++---- .../autopatches/20150525.diff.hidden.1.sql | 7 ++ src/__phutil_library_map__.php | 4 + .../storage/PhabricatorAuditInlineComment.php | 8 ++ .../DifferentialChangesetViewController.php | 1 + ...ifferentialInlineCommentEditController.php | 36 +++++++ ...erentialInlineCommentPreviewController.php | 1 + .../DifferentialRevisionViewController.php | 1 + .../query/DifferentialInlineCommentQuery.php | 26 +++++ .../DifferentialChangesetOneUpRenderer.php | 48 ++++++++- .../DifferentialChangesetTwoUpRenderer.php | 101 ++++++++++++------ .../storage/DifferentialHiddenComment.php | 24 +++++ .../storage/DifferentialInlineComment.php | 9 ++ .../storage/DifferentialRevision.php | 1 + .../DifferentialTransactionComment.php | 10 ++ .../view/DifferentialChangesetListView.php | 5 +- .../PhabricatorInlineCommentController.php | 24 +++++ .../PhabricatorInlineCommentInterface.php | 3 + .../view/PHUIDiffInlineCommentDetailView.php | 18 ++++ .../view/PHUIDiffInlineCommentRowScaffold.php | 10 ++ .../diff/view/PHUIDiffInlineCommentView.php | 4 + .../PHUIDiffOneUpInlineCommentRowScaffold.php | 2 +- .../diff/view/PHUIDiffRevealIconView.php | 27 +++++ .../PHUIDiffTwoUpInlineCommentRowScaffold.php | 2 +- .../differential/phui-inline-comment.css | 18 ++++ .../behavior-edit-inline-comments.js | 83 ++++++++++++++ 26 files changed, 454 insertions(+), 65 deletions(-) create mode 100644 resources/sql/autopatches/20150525.diff.hidden.1.sql create mode 100644 src/applications/differential/storage/DifferentialHiddenComment.php create mode 100644 src/infrastructure/diff/view/PHUIDiffRevealIconView.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index bf3f6f59fa..f9ebfb461b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,8 +10,8 @@ return array( 'core.pkg.css' => '439658b5', 'core.pkg.js' => '328799d0', 'darkconsole.pkg.js' => 'e7393ebb', - 'differential.pkg.css' => 'bb338e4b', - 'differential.pkg.js' => '63a77807', + 'differential.pkg.css' => '30602b8c', + 'differential.pkg.js' => '8c98ce21', 'diffusion.pkg.css' => '591664fa', 'diffusion.pkg.js' => '0115b37c', 'maniphest.pkg.css' => '68d4dd3d', @@ -60,7 +60,7 @@ return array( 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 'rsrc/css/application/differential/changeset-view.css' => 'e19cfd6e', 'rsrc/css/application/differential/core.css' => '7ac3cabc', - 'rsrc/css/application/differential/phui-inline-comment.css' => '2174771a', + 'rsrc/css/application/differential/phui-inline-comment.css' => 'aa16f165', 'rsrc/css/application/differential/results-table.css' => '181aa9d9', 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', 'rsrc/css/application/differential/revision-history.css' => '0e8eb855', @@ -353,7 +353,7 @@ return array( 'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76', 'rsrc/js/application/differential/behavior-diff-radios.js' => 'e1ff79b1', 'rsrc/js/application/differential/behavior-dropdown-menus.js' => '2035b9cb', - 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => 'e723c323', + 'rsrc/js/application/differential/behavior-edit-inline-comments.js' => '037b59eb', 'rsrc/js/application/differential/behavior-keyboard-nav.js' => '2c426492', 'rsrc/js/application/differential/behavior-populate.js' => '8694b1df', 'rsrc/js/application/differential/behavior-show-field-details.js' => 'bba9eedf', @@ -443,7 +443,7 @@ return array( 'rsrc/js/core/behavior-device.js' => 'a205cf28', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', - 'rsrc/js/core/behavior-fancy-datepicker.js' => '2d4029a8', + 'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', @@ -560,7 +560,7 @@ return array( 'javelin-behavior-differential-comment-jump' => '4fdb476d', 'javelin-behavior-differential-diff-radios' => 'e1ff79b1', 'javelin-behavior-differential-dropdown-menus' => '2035b9cb', - 'javelin-behavior-differential-edit-inline-comments' => 'e723c323', + 'javelin-behavior-differential-edit-inline-comments' => '037b59eb', 'javelin-behavior-differential-feedback-preview' => 'b064af76', 'javelin-behavior-differential-keyboard-navigation' => '2c426492', 'javelin-behavior-differential-populate' => '8694b1df', @@ -576,7 +576,7 @@ return array( 'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => '38dcf3c8', - 'javelin-behavior-fancy-datepicker' => '2d4029a8', + 'javelin-behavior-fancy-datepicker' => '5c0f680f', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', @@ -782,7 +782,7 @@ return array( 'phui-image-mask-css' => '5a8b09c8', 'phui-info-panel-css' => '27ea50a1', 'phui-info-view-css' => 'c6f0aef8', - 'phui-inline-comment-view-css' => '2174771a', + 'phui-inline-comment-view-css' => 'aa16f165', 'phui-list-view-css' => '2e25ebfb', 'phui-object-box-css' => '7d160002', 'phui-object-item-list-view-css' => 'f3a22696', @@ -830,6 +830,14 @@ return array( '029a133d' => array( 'aphront-dialog-view-css', ), + '037b59eb' => array( + 'javelin-behavior', + 'javelin-stratcom', + 'javelin-dom', + 'javelin-util', + 'javelin-vector', + 'differential-inline-comment-editor', + ), '048330fa' => array( 'javelin-behavior', 'javelin-typeahead-ondemand-source', @@ -1042,13 +1050,6 @@ return array( 'javelin-install', 'javelin-event', ), - '2d4029a8' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), '331b1611' => array( 'javelin-install', ), @@ -1241,6 +1242,13 @@ return array( 'javelin-uri', 'javelin-routable', ), + '5c0f680f' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1925,14 +1933,6 @@ return array( 'e6e25838' => array( 'javelin-install', ), - 'e723c323' => array( - 'javelin-behavior', - 'javelin-stratcom', - 'javelin-dom', - 'javelin-util', - 'javelin-vector', - 'differential-inline-comment-editor', - ), 'e9581f08' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/resources/sql/autopatches/20150525.diff.hidden.1.sql b/resources/sql/autopatches/20150525.diff.hidden.1.sql new file mode 100644 index 0000000000..d6b3df6440 --- /dev/null +++ b/resources/sql/autopatches/20150525.diff.hidden.1.sql @@ -0,0 +1,7 @@ +CREATE TABLE {$NAMESPACE}_differential.differential_hiddencomment ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + userPHID VARBINARY(64) NOT NULL, + commentID INT UNSIGNED NOT NULL, + UNIQUE KEY `key_user` (userPHID, commentID), + KEY `key_comment` (commentID) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 6a67775a9a..773867f271 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -374,6 +374,7 @@ phutil_register_library_map(array( 'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php', 'DifferentialGitHubLandingStrategy' => 'applications/differential/landing/DifferentialGitHubLandingStrategy.php', 'DifferentialGitSVNIDField' => 'applications/differential/customfield/DifferentialGitSVNIDField.php', + 'DifferentialHiddenComment' => 'applications/differential/storage/DifferentialHiddenComment.php', 'DifferentialHostField' => 'applications/differential/customfield/DifferentialHostField.php', 'DifferentialHostedGitLandingStrategy' => 'applications/differential/landing/DifferentialHostedGitLandingStrategy.php', 'DifferentialHostedMercurialLandingStrategy' => 'applications/differential/landing/DifferentialHostedMercurialLandingStrategy.php', @@ -1178,6 +1179,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php', 'PHUIDiffInlineCommentView' => 'infrastructure/diff/view/PHUIDiffInlineCommentView.php', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php', + 'PHUIDiffRevealIconView' => 'infrastructure/diff/view/PHUIDiffRevealIconView.php', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php', 'PHUIDocumentExample' => 'applications/uiexample/examples/PHUIDocumentExample.php', 'PHUIDocumentView' => 'view/phui/PHUIDocumentView.php', @@ -3616,6 +3618,7 @@ phutil_register_library_map(array( 'DifferentialGetRevisionConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialGitHubLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialGitSVNIDField' => 'DifferentialCustomField', + 'DifferentialHiddenComment' => 'DifferentialDAO', 'DifferentialHostField' => 'DifferentialCustomField', 'DifferentialHostedGitLandingStrategy' => 'DifferentialLandingStrategy', 'DifferentialHostedMercurialLandingStrategy' => 'DifferentialLandingStrategy', @@ -4506,6 +4509,7 @@ phutil_register_library_map(array( 'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView', 'PHUIDiffInlineCommentView' => 'AphrontView', 'PHUIDiffOneUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', + 'PHUIDiffRevealIconView' => 'AphrontView', 'PHUIDiffTwoUpInlineCommentRowScaffold' => 'PHUIDiffInlineCommentRowScaffold', 'PHUIDocumentExample' => 'PhabricatorUIExample', 'PHUIDocumentView' => 'AphrontTagView', diff --git a/src/applications/audit/storage/PhabricatorAuditInlineComment.php b/src/applications/audit/storage/PhabricatorAuditInlineComment.php index 292e0274a5..8779b5ffdd 100644 --- a/src/applications/audit/storage/PhabricatorAuditInlineComment.php +++ b/src/applications/audit/storage/PhabricatorAuditInlineComment.php @@ -23,6 +23,14 @@ final class PhabricatorAuditInlineComment return $this->proxy; } + public function supportsHiding() { + return false; + } + + public function isHidden() { + return false; + } + public function getTransactionCommentForSave() { $content_source = PhabricatorContentSource::newForSource( PhabricatorContentSource::SOURCE_LEGACY, diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php index 0638578f94..2373d3ef2c 100644 --- a/src/applications/differential/controller/DifferentialChangesetViewController.php +++ b/src/applications/differential/controller/DifferentialChangesetViewController.php @@ -191,6 +191,7 @@ final class DifferentialChangesetViewController extends DifferentialController { if ($revision) { $query = id(new DifferentialInlineCommentQuery()) ->setViewer($viewer) + ->needHidden(true) ->withRevisionPHIDs(array($revision->getPHID())); $inlines = $query->execute(); $inlines = $query->adjustInlinesForChangesets( diff --git a/src/applications/differential/controller/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/DifferentialInlineCommentEditController.php index 13aac1776c..deba6af01b 100644 --- a/src/applications/differential/controller/DifferentialInlineCommentEditController.php +++ b/src/applications/differential/controller/DifferentialInlineCommentEditController.php @@ -42,6 +42,7 @@ final class DifferentialInlineCommentEditController ->setViewer($this->getViewer()) ->withIDs(array($id)) ->withDeletedDrafts(true) + ->needHidden(true) ->executeOne(); } @@ -50,6 +51,7 @@ final class DifferentialInlineCommentEditController ->setViewer($this->getViewer()) ->withPHIDs(array($phid)) ->withDeletedDrafts(true) + ->needHidden(true) ->executeOne(); } @@ -152,4 +154,38 @@ final class DifferentialInlineCommentEditController return $this->loadRevision()->getAuthorPHID(); } + protected function hideComments(array $ids) { + $viewer = $this->getViewer(); + $table = new DifferentialHiddenComment(); + $conn_w = $table->establishConnection('w'); + + $sql = array(); + foreach ($ids as $id) { + $sql[] = qsprintf( + $conn_w, + '(%s, %d)', + $viewer->getPHID(), + $id); + } + + queryfx( + $conn_w, + 'INSERT IGNORE INTO %T (userPHID, commentID) VALUES %Q', + $table->getTableName(), + implode(', ', $sql)); + } + + protected function showComments(array $ids) { + $viewer = $this->getViewer(); + $table = new DifferentialHiddenComment(); + $conn_w = $table->establishConnection('w'); + + queryfx( + $conn_w, + 'DELETE FROM %T WHERE userPHID = %s AND commentID IN (%Ld)', + $table->getTableName(), + $viewer->getPHID(), + $ids); + } + } diff --git a/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php b/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php index 846cab5439..519b08bb63 100644 --- a/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php +++ b/src/applications/differential/controller/DifferentialInlineCommentPreviewController.php @@ -19,6 +19,7 @@ extends PhabricatorInlineCommentPreviewController { ->withDrafts(true) ->withAuthorPHIDs(array($viewer->getPHID())) ->withRevisionPHIDs(array($revision->getPHID())) + ->needHidden(true) ->execute(); } diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php index 312d4a7f4e..7069e5ae97 100644 --- a/src/applications/differential/controller/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/DifferentialRevisionViewController.php @@ -175,6 +175,7 @@ final class DifferentialRevisionViewController extends DifferentialController { $query = id(new DifferentialInlineCommentQuery()) ->setViewer($user) + ->needHidden(true) ->withRevisionPHIDs(array($revision->getPHID())); $inlines = $query->execute(); $inlines = $query->adjustInlinesForChangesets( diff --git a/src/applications/differential/query/DifferentialInlineCommentQuery.php b/src/applications/differential/query/DifferentialInlineCommentQuery.php index 3fb26739c0..7e986bdc60 100644 --- a/src/applications/differential/query/DifferentialInlineCommentQuery.php +++ b/src/applications/differential/query/DifferentialInlineCommentQuery.php @@ -15,6 +15,7 @@ final class DifferentialInlineCommentQuery private $authorPHIDs; private $revisionPHIDs; private $deletedDrafts; + private $needHidden; public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; @@ -55,6 +56,11 @@ final class DifferentialInlineCommentQuery return $this; } + public function needHidden($need) { + $this->needHidden = $need; + return $this; + } + public function execute() { $table = new DifferentialTransactionComment(); $conn_r = $table->establishConnection('r'); @@ -68,6 +74,26 @@ final class DifferentialInlineCommentQuery $comments = $table->loadAllFromArray($data); + if ($this->needHidden) { + $viewer_phid = $this->getViewer()->getPHID(); + if ($viewer_phid && $comments) { + $hidden = queryfx_all( + $conn_r, + 'SELECT commentID FROM %T WHERE userPHID = %s + AND commentID IN (%Ls)', + id(new DifferentialHiddenComment())->getTableName(), + $viewer_phid, + mpull($comments, 'getID')); + $hidden = array_fuse(ipull($hidden, 'commentID')); + } else { + $hidden = array(); + } + + foreach ($comments as $inline) { + $inline->attachIsHidden(isset($hidden[$inline->getID()])); + } + } + foreach ($comments as $key => $value) { $comments[$key] = DifferentialInlineComment::newFromModernComment( $value); diff --git a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php index 2634d037d7..dfc990d4d4 100644 --- a/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetOneUpRenderer.php @@ -41,8 +41,10 @@ final class DifferentialChangesetOneUpRenderer $column_width = 4; + $hidden = new PHUIDiffRevealIconView(); + $out = array(); - foreach ($primitives as $p) { + foreach ($primitives as $k => $p) { $type = $p['type']; switch ($type) { case 'old': @@ -51,6 +53,27 @@ final class DifferentialChangesetOneUpRenderer case 'new-file': $is_old = ($type == 'old' || $type == 'old-file'); + $o_hidden = array(); + $n_hidden = array(); + + for ($look = $k + 1; isset($primitives[$look]); $look++) { + $next = $primitives[$look]; + switch ($next['type']) { + case 'inline': + $comment = $next['comment']; + if ($comment->isHidden()) { + if ($next['right']) { + $n_hidden[] = $comment; + } else { + $o_hidden[] = $comment; + } + } + break; + default: + break 2; + } + } + $cells = array(); if ($is_old) { if ($p['htype']) { @@ -68,7 +91,13 @@ final class DifferentialChangesetOneUpRenderer } else { $left_id = null; } - $cells[] = phutil_tag('th', array('id' => $left_id), $p['line']); + + $line = $p['line']; + if ($o_hidden) { + $line = array($hidden, $line); + } + + $cells[] = phutil_tag('th', array('id' => $left_id), $line); $cells[] = phutil_tag('th', array()); $cells[] = $no_copy; @@ -85,7 +114,13 @@ final class DifferentialChangesetOneUpRenderer } else { $left_id = null; } - $cells[] = phutil_tag('th', array('id' => $left_id), $p['oline']); + + $oline = $p['oline']; + if ($o_hidden) { + $oline = array($hidden, $oline); + } + + $cells[] = phutil_tag('th', array('id' => $left_id), $oline); } if ($type == 'new-file') { @@ -97,8 +132,13 @@ final class DifferentialChangesetOneUpRenderer } else { $right_id = null; } - $cells[] = phutil_tag('th', array('id' => $right_id), $p['line']); + $line = $p['line']; + if ($n_hidden) { + $line = array($hidden, $line); + } + + $cells[] = phutil_tag('th', array('id' => $right_id), $line); $cells[] = $no_copy; $cells[] = phutil_tag('td', array('class' => $class), $p['render']); diff --git a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php index 181e3d1306..16752b9402 100644 --- a/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php +++ b/src/applications/differential/render/DifferentialChangesetTwoUpRenderer.php @@ -69,6 +69,8 @@ final class DifferentialChangesetTwoUpRenderer $depths = $this->getDepths(); $mask = $this->getMask(); + $hidden = new PHUIDiffRevealIconView(); + for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) { if (empty($mask[$ii])) { // If we aren't going to show this line, we've just entered a gap. @@ -235,6 +237,69 @@ final class DifferentialChangesetTwoUpRenderer $n_id = null; } + $old_comments = $this->getOldComments(); + $new_comments = $this->getNewComments(); + $scaffolds = array(); + + $o_hidden = array(); + $n_hidden = array(); + + if ($o_num && isset($old_comments[$o_num])) { + foreach ($old_comments[$o_num] as $comment) { + $inline = $this->buildInlineComment( + $comment, + $on_right = false); + $scaffold = $this->getRowScaffoldForInline($inline); + + if ($comment->isHidden()) { + $o_hidden[] = $comment; + } + + if ($n_num && isset($new_comments[$n_num])) { + foreach ($new_comments[$n_num] as $key => $new_comment) { + if ($comment->isCompatible($new_comment)) { + $companion = $this->buildInlineComment( + $new_comment, + $on_right = true); + + if ($new_comment->isHidden()) { + $n_hidden = $new_comment; + } + + $scaffold->addInlineView($companion); + unset($new_comments[$n_num][$key]); + break; + } + } + } + + + $scaffolds[] = $scaffold; + } + } + + if ($n_num && isset($new_comments[$n_num])) { + foreach ($new_comments[$n_num] as $comment) { + $inline = $this->buildInlineComment( + $comment, + $on_right = true); + + if ($comment->isHidden()) { + $n_hidden[] = $comment; + } + + $scaffolds[] = $this->getRowScaffoldForInline($inline); + } + } + + if ($o_hidden) { + $o_num = array($hidden, $o_num); + } + + if ($n_hidden) { + $n_num = array($hidden, $n_num); + } + // NOTE: This is a unicode zero-width space, which we use as a hint when // intercepting 'copy' events to make sure sensible text ends up on the // clipboard. See the 'phabricator-oncopy' behavior. @@ -259,40 +324,8 @@ final class DifferentialChangesetTwoUpRenderer $html[] = $context_not_available; } - $old_comments = $this->getOldComments(); - $new_comments = $this->getNewComments(); - - if ($o_num && isset($old_comments[$o_num])) { - foreach ($old_comments[$o_num] as $comment) { - $inline = $this->buildInlineComment( - $comment, - $on_right = false); - $scaffold = $this->getRowScaffoldForInline($inline); - - if ($n_num && isset($new_comments[$n_num])) { - foreach ($new_comments[$n_num] as $key => $new_comment) { - if ($comment->isCompatible($new_comment)) { - $companion = $this->buildInlineComment( - $new_comment, - $on_right = true); - - $scaffold->addInlineView($companion); - unset($new_comments[$n_num][$key]); - break; - } - } - } - - $html[] = $scaffold; - } - } - if ($n_num && isset($new_comments[$n_num])) { - foreach ($new_comments[$n_num] as $comment) { - $inline = $this->buildInlineComment( - $comment, - $on_right = true); - $html[] = $this->getRowScaffoldForInline($inline); - } + foreach ($scaffolds as $scaffold) { + $html[] = $scaffold; } } diff --git a/src/applications/differential/storage/DifferentialHiddenComment.php b/src/applications/differential/storage/DifferentialHiddenComment.php new file mode 100644 index 0000000000..90dbb527f2 --- /dev/null +++ b/src/applications/differential/storage/DifferentialHiddenComment.php @@ -0,0 +1,24 @@ + false, + self::CONFIG_KEY_SCHEMA => array( + 'key_user' => array( + 'columns' => array('userPHID', 'commentID'), + 'unique' => true, + ), + 'key_comment' => array( + 'columns' => array('commentID'), + ), + ), + ) + parent::getConfiguration(); + } + +} diff --git a/src/applications/differential/storage/DifferentialInlineComment.php b/src/applications/differential/storage/DifferentialInlineComment.php index 65ea64773b..979220fc02 100644 --- a/src/applications/differential/storage/DifferentialInlineComment.php +++ b/src/applications/differential/storage/DifferentialInlineComment.php @@ -24,6 +24,7 @@ final class DifferentialInlineComment ->setViewPolicy('public') ->setEditPolicy($this->getAuthorPHID()) ->setContentSource($content_source) + ->attachIsHidden(false) ->setCommentVersion(1); return $this->proxy; @@ -49,6 +50,14 @@ final class DifferentialInlineComment return $this; } + public function supportsHiding() { + return true; + } + + public function isHidden() { + return $this->proxy->getIsHidden(); + } + public function getID() { return $this->proxy->getID(); } diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index 7630814882..0a4d459568 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -522,6 +522,7 @@ final class DifferentialRevision extends DifferentialDAO } $query = id(new DifferentialInlineCommentQuery()) + ->needHidden(true) ->setViewer($viewer); // NOTE: This is a bit sketchy: this method adjusts the inlines as a diff --git a/src/applications/differential/storage/DifferentialTransactionComment.php b/src/applications/differential/storage/DifferentialTransactionComment.php index 0d1e6ea33a..5a9b031dad 100644 --- a/src/applications/differential/storage/DifferentialTransactionComment.php +++ b/src/applications/differential/storage/DifferentialTransactionComment.php @@ -13,6 +13,7 @@ final class DifferentialTransactionComment protected $replyToCommentPHID; private $replyToComment = self::ATTACHABLE; + private $isHidden = self::ATTACHABLE; public function getApplicationTransactionObject() { return new DifferentialTransaction(); @@ -99,4 +100,13 @@ final class DifferentialTransactionComment return $inline_groups; } + public function getIsHidden() { + return $this->assertAttached($this->isHidden); + } + + public function attachIsHidden($hidden) { + $this->isHidden = $hidden; + return $this; + } + } diff --git a/src/applications/differential/view/DifferentialChangesetListView.php b/src/applications/differential/view/DifferentialChangesetListView.php index 3e7bfb677c..0dd40f39c3 100644 --- a/src/applications/differential/view/DifferentialChangesetListView.php +++ b/src/applications/differential/view/DifferentialChangesetListView.php @@ -232,8 +232,9 @@ final class DifferentialChangesetListView extends AphrontView { if ($this->inlineURI) { Javelin::initBehavior('differential-edit-inline-comments', array( - 'uri' => $this->inlineURI, - 'stage' => 'differential-review-stage', + 'uri' => $this->inlineURI, + 'stage' => 'differential-review-stage', + 'revealIcon' => hsprintf('%s', new PHUIDiffRevealIconView()), )); } diff --git a/src/infrastructure/diff/PhabricatorInlineCommentController.php b/src/infrastructure/diff/PhabricatorInlineCommentController.php index 4608ebeffa..343319b3d8 100644 --- a/src/infrastructure/diff/PhabricatorInlineCommentController.php +++ b/src/infrastructure/diff/PhabricatorInlineCommentController.php @@ -15,6 +15,14 @@ abstract class PhabricatorInlineCommentController abstract protected function saveComment( PhabricatorInlineCommentInterface $inline); + protected function hideComments(array $ids) { + throw new PhutilMethodNotImplementedException(); + } + + protected function showComments(array $ids) { + throw new PhutilMethodNotImplementedException(); + } + private $changesetID; private $isNewFile; private $isOnRight; @@ -84,6 +92,22 @@ abstract class PhabricatorInlineCommentController $op = $this->getOperation(); switch ($op) { + case 'hide': + case 'show': + if (!$request->validateCSRF()) { + return new Aphront404Response(); + } + + $ids = $request->getStrList('ids'); + if ($ids) { + if ($op == 'hide') { + $this->hideComments($ids); + } else { + $this->showComments($ids); + } + } + + return id(new AphrontAjaxResponse())->setContent(array()); case 'done': if (!$request->validateCSRF()) { return new Aphront404Response(); diff --git a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php index 8ba648f2b6..13bf3ad83b 100644 --- a/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php +++ b/src/infrastructure/diff/interface/PhabricatorInlineCommentInterface.php @@ -57,4 +57,7 @@ interface PhabricatorInlineCommentInterface extends PhabricatorMarkupInterface { public function setIsGhost($is_ghost); public function getIsGhost(); + public function supportsHiding(); + public function isHidden(); + } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php index e265cafad1..de17b5dd2b 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php @@ -18,6 +18,10 @@ final class PHUIDiffInlineCommentDetailView return $this; } + public function isHidden() { + return $this->inlineComment->isHidden(); + } + public function setHandles(array $handles) { assert_instances_of($handles, 'PhabricatorObjectHandle'); $this->handles = $handles; @@ -192,6 +196,8 @@ final class PHUIDiffInlineCommentDetailView if (!$this->preview) { $nextprev = new PHUIButtonBarView(); $nextprev->addClass('mml'); + + $up = id(new PHUIButtonView()) ->setTag('a') ->setColor(PHUIButtonView::SIMPLE) @@ -208,6 +214,18 @@ final class PHUIDiffInlineCommentDetailView ->addSigil('differential-inline-next') ->setMustCapture(true); + $hide = id(new PHUIButtonView()) + ->setTag('a') + ->setColor(PHUIButtonView::SIMPLE) + ->setTooltip(pht('Hide Comment')) + ->setIconFont('fa-times') + ->addSigil('hide-inline') + ->setMustCapture(true); + + if ($viewer_phid && $inline->getID() && $inline->supportsHiding()) { + $nextprev->addButton($hide); + } + $nextprev->addButton($up); $nextprev->addButton($down); diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php index 194ff6f5cc..b0c0b4ccac 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php @@ -23,8 +23,18 @@ abstract class PHUIDiffInlineCommentRowScaffold extends AphrontView { protected function getRowAttributes() { // TODO: This is semantic information used by the JS when placing comments // and using keyboard navigation; we should move it out of class names. + + $style = null; + foreach ($this->getInlineViews() as $view) { + if ($view->isHidden()) { + $style = 'display: none'; + } + } + return array( 'class' => 'inline', + 'sigil' => 'inline-row', + 'style' => $style, ); } diff --git a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php index f2f82471d6..b62160e232 100644 --- a/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php +++ b/src/infrastructure/diff/view/PHUIDiffInlineCommentView.php @@ -17,4 +17,8 @@ abstract class PHUIDiffInlineCommentView extends AphrontView { return null; } + public function isHidden() { + return false; + } + } diff --git a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php index 6b8cb0a32e..708b70b360 100644 --- a/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffOneUpInlineCommentRowScaffold.php @@ -28,7 +28,7 @@ final class PHUIDiffOneUpInlineCommentRowScaffold phutil_tag('td', $attrs, $inline), ); - return phutil_tag('tr', $this->getRowAttributes(), $cells); + return javelin_tag('tr', $this->getRowAttributes(), $cells); } } diff --git a/src/infrastructure/diff/view/PHUIDiffRevealIconView.php b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php new file mode 100644 index 0000000000..b2c879a8b5 --- /dev/null +++ b/src/infrastructure/diff/view/PHUIDiffRevealIconView.php @@ -0,0 +1,27 @@ +setIconFont('fa-comment') + ->addSigil('has-tooltip') + ->setMetadata( + array( + 'tip' => pht('Show Hidden Comments'), + 'align' => 'E', + 'size' => 275, + )); + + return javelin_tag( + 'a', + array( + 'href' => '#', + 'class' => 'reveal-inlines', + 'sigil' => 'reveal-inlines', + 'mustcapture' => true, + ), + $icon); + } + +} diff --git a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php index e2c2183856..4fac5088d1 100644 --- a/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php +++ b/src/infrastructure/diff/view/PHUIDiffTwoUpInlineCommentRowScaffold.php @@ -68,7 +68,7 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold phutil_tag('td', $right_attrs, $right_side), ); - return phutil_tag('tr', $this->getRowAttributes(), $cells); + return javelin_tag('tr', $this->getRowAttributes(), $cells); } } diff --git a/webroot/rsrc/css/application/differential/phui-inline-comment.css b/webroot/rsrc/css/application/differential/phui-inline-comment.css index 5b6e2d79ee..632635ba3a 100644 --- a/webroot/rsrc/css/application/differential/phui-inline-comment.css +++ b/webroot/rsrc/css/application/differential/phui-inline-comment.css @@ -434,3 +434,21 @@ border-color: {$lightgreyborder}; color: {$lightgreytext}; } + + +/* - Hiding Inlines ------------------------------------------------------------ +*/ + +.reveal-inlines { + float: left; + margin-left: 4px; + color: {$lightbluetext}; +} + +.reveal-inlines span.phui-icon-view { + color: {$lightbluetext}; +} + +.reveal-inlines:hover span.phui-icon-view { + color: {$darkbluetext}; +} diff --git a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js index 0a572a999a..821d279f50 100644 --- a/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js +++ b/webroot/rsrc/js/application/differential/behavior-edit-inline-comments.js @@ -395,4 +395,87 @@ JX.behavior('differential-edit-inline-comments', function(config) { handle_inline_action(data.node, data.op); }); + // Respond to the user clicking the "Hide Inline" button on an inline + // comment. + JX.Stratcom.listen('click', 'hide-inline', function(e) { + e.kill(); + + var row = e.getNode('inline-row'); + JX.DOM.hide(row); + + var prev = row.previousSibling; + while (prev && JX.Stratcom.hasSigil(prev, 'inline-row')) { + prev = prev.previousSibling; + } + + if (!prev) { + return; + } + + var comment = e.getNodeData('differential-inline-comment'); + + var slots = []; + for (var ii = 0; ii < prev.childNodes.length; ii++) { + if (JX.DOM.isType(prev.childNodes[ii], 'th')) { + slots.push(prev.childNodes[ii]); + } + } + + // Select the right-hand side if the comment is on the right. + var slot = (comment.on_right && slots[1]) || slots[0]; + + var reveal = JX.DOM.scry(slot, 'a', 'reveal-inlines')[0]; + if (!reveal) { + reveal = JX.$N( + 'a', + { + className: 'reveal-inlines', + sigil: 'reveal-inlines' + }, + JX.$H(config.revealIcon)); + + JX.DOM.prependContent(slot, reveal); + } + + new JX.Workflow(config.uri, {op: 'hide', ids: comment.id}) + .setHandler(JX.bag) + .start(); + }); + + JX.Stratcom.listen('click', 'reveal-inlines', function(e) { + e.kill(); + + var row = e.getNode('tag:tr'); + var next = row.nextSibling; + + var ids = []; + var ii; + + // Show any hidden inline comment rows directly below this one. + while (next && JX.Stratcom.hasSigil(next, 'inline-row')) { + JX.DOM.show(next); + + var comments = JX.DOM.scry(next, 'div', 'differential-inline-comment'); + for (ii = 0; ii < comments.length; ii++) { + var id = JX.Stratcom.getData(comments[ii]).id; + if (id) { + ids.push(id); + } + } + + next = next.nextSibling; + } + + // Remove any "reveal" icons on the row. + var reveals = JX.DOM.scry(row, 'a', 'reveal-inlines'); + for (ii = 0; ii < reveals.length; ii++) { + JX.DOM.remove(reveals[ii]); + } + + new JX.Workflow(config.uri, {op: 'show', ids: ids.join(',')}) + .setHandler(JX.bag) + .start(); + }); + + }); From 6d5d34e6a87b1c9e26cd13f0fbe694f37e5420c1 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:29:01 -0700 Subject: [PATCH 36/43] Use ApplicationSearch in Owners Summary: Ref T8320. Modernize search and major interfaces. This slightly regresses some list view and search features; I'll probably restore some later (once the Query has proper `needX(...)` methods) and drop the rest. Test Plan: Browsed, edited, deleted, and created packages. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8320 Differential Revision: https://secure.phabricator.com/D13024 --- src/__phutil_library_map__.php | 2 + .../PhabricatorOwnersApplication.php | 3 +- .../PhabricatorOwnersController.php | 69 +--- .../PhabricatorOwnersDetailController.php | 43 +-- .../PhabricatorOwnersEditController.php | 50 +-- .../PhabricatorOwnersListController.php | 357 ++---------------- .../query/PhabricatorOwnersPackageQuery.php | 39 +- .../PhabricatorOwnersPackageSearchEngine.php | 134 +++++++ .../storage/PhabricatorOwnersPackage.php | 5 +- 9 files changed, 247 insertions(+), 455 deletions(-) create mode 100644 src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 773867f271..faf31ec97b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2178,6 +2178,7 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', 'PhabricatorOwnersPackagePathValidator' => 'applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', + 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', @@ -5584,6 +5585,7 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackageEditor' => 'PhabricatorEditor', 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/owners/application/PhabricatorOwnersApplication.php b/src/applications/owners/application/PhabricatorOwnersApplication.php index ba44293016..97b3adf49b 100644 --- a/src/applications/owners/application/PhabricatorOwnersApplication.php +++ b/src/applications/owners/application/PhabricatorOwnersApplication.php @@ -42,8 +42,7 @@ final class PhabricatorOwnersApplication extends PhabricatorApplication { public function getRoutes() { return array( '/owners/' => array( - '' => 'PhabricatorOwnersListController', - 'view/(?P[^/]+)/' => 'PhabricatorOwnersListController', + '(?:query/(?P[^/]+)/)?' => 'PhabricatorOwnersListController', 'edit/(?P[1-9]\d*)/' => 'PhabricatorOwnersEditController', 'new/' => 'PhabricatorOwnersEditController', 'package/(?P[1-9]\d*)/' => 'PhabricatorOwnersDetailController', diff --git a/src/applications/owners/controller/PhabricatorOwnersController.php b/src/applications/owners/controller/PhabricatorOwnersController.php index 4066b297dc..aa2e618775 100644 --- a/src/applications/owners/controller/PhabricatorOwnersController.php +++ b/src/applications/owners/controller/PhabricatorOwnersController.php @@ -1,70 +1,3 @@ filter; - } - protected function setSideNavFilter($filter) { - $this->filter = $filter; - return $this; - } - - public function buildSideNavView() { - $nav = new AphrontSideNavFilterView(); - $base_uri = new PhutilURI('/owners/'); - $nav->setBaseURI($base_uri); - - $nav->addLabel(pht('Packages')); - $this->getExtraPackageViews($nav); - $nav->addFilter('view/owned', pht('Owned')); - $nav->addFilter('view/projects', pht('Projects')); - $nav->addFilter('view/all', pht('All')); - - $nav->selectFilter($this->getSideNavFilter(), 'view/owned'); - - $filter = $nav->getSelectedFilter(); - switch ($filter) { - case 'view/owned': - $title = pht('Owned Packages'); - break; - case 'view/all': - $title = pht('All Packages'); - break; - case 'view/projects': - $title = pht('Projects'); - break; - case 'new': - $title = pht('New Package'); - break; - default: - $title = pht('Package'); - break; - } - - return $nav; - } - - protected function buildApplicationCrumbs() { - $crumbs = parent::buildApplicationCrumbs(); - - $crumbs->addAction( - id(new PHUIListItemView()) - ->setName(pht('Create Package')) - ->setHref('/owners/new/') - ->setIcon('fa-plus-square')); - - return $crumbs; - } - - public function buildApplicationMenu() { - return $this->buildSideNavView()->getMenu(); - } - - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - return; - } - -} +abstract class PhabricatorOwnersController extends PhabricatorController {} diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 0b50db02d5..4193f31972 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -3,22 +3,20 @@ final class PhabricatorOwnersDetailController extends PhabricatorOwnersController { - private $id; - private $package; - - public function willProcessRequest(array $data) { - $this->id = $data['id']; + public function shouldAllowPublic() { + return true; } - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); + public function handleRequest(AphrontRequest $request) { + $viewer = $this->getViewer(); - $package = id(new PhabricatorOwnersPackage())->load($this->id); + $package = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->executeOne(); if (!$package) { return new Aphront404Response(); } - $this->package = $package; $paths = $package->loadPaths(); $owners = $package->loadOwners(); @@ -30,7 +28,7 @@ final class PhabricatorOwnersDetailController if ($repository_phids) { $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) + ->setViewer($viewer) ->withPHIDs(array_keys($repository_phids)) ->execute(); $repositories = mpull($repositories, null, 'getPHID'); @@ -131,9 +129,6 @@ final class PhabricatorOwnersDetailController $panel->setHeader($header); $panel->appendChild($table); - $key = 'package/'.$package->getID(); - $this->setSideNavFilter($key); - $commit_views = array(); $commit_uri = id(new PhutilURI('/audit/')) @@ -151,7 +146,7 @@ final class PhabricatorOwnersDetailController ->execute(); if ($attention_commits) { $view = id(new PhabricatorAuditListView()) - ->setUser($user) + ->setUser($viewer) ->setCommits($attention_commits); $commit_views[] = array( @@ -172,7 +167,7 @@ final class PhabricatorOwnersDetailController ->execute(); $view = id(new PhabricatorAuditListView()) - ->setUser($user) + ->setUser($viewer) ->setCommits($all_commits) ->setNoDataString(pht('No commits in this package.')); @@ -210,21 +205,15 @@ final class PhabricatorOwnersDetailController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($package->getName()); - $nav = $this->buildSideNavView(); - $nav->appendChild($crumbs); - $nav->appendChild($panel); - $nav->appendChild($commit_panels); - return $this->buildApplicationPage( - $nav, + array( + $crumbs, + $panel, + $commit_panels, + ), array( 'title' => pht('Package %s', $package->getName()), )); } - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - $package = $this->package; - $view->addFilter('package/'.$package->getID(), pht('Details')); - } - } diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 3a0baaa06e..717d8faacd 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -3,24 +3,27 @@ final class PhabricatorOwnersEditController extends PhabricatorOwnersController { - private $id; + public function handleRequest(AphrontRequest $request) { + $viewer = $request->getUser(); - public function willProcessRequest(array $data) { - $this->id = idx($data, 'id'); - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - if ($this->id) { - $package = id(new PhabricatorOwnersPackage())->load($this->id); + $id = $request->getURIData('id'); + if ($id) { + $package = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withIDs(array($id)) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + // TODO: Support this capability. + // PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); if (!$package) { return new Aphront404Response(); } } else { $package = new PhabricatorOwnersPackage(); - $package->setPrimaryOwnerPHID($user->getPHID()); + $package->setPrimaryOwnerPHID($viewer->getPHID()); } $e_name = true; @@ -89,7 +92,7 @@ final class PhabricatorOwnersEditController $package->attachOldPrimaryOwnerPHID($old_primary); try { id(new PhabricatorOwnersPackageEditor()) - ->setActor($user) + ->setActor($viewer) ->setPackage($package) ->save(); return id(new AphrontRedirectResponse()) @@ -123,15 +126,12 @@ final class PhabricatorOwnersEditController if ($package->getID()) { $title = pht('Edit Package'); - $side_nav_filter = 'edit/'.$this->id; } else { $title = pht('New Package'); - $side_nav_filter = 'new'; } - $this->setSideNavFilter($side_nav_filter); $repos = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) + ->setViewer($viewer) ->execute(); $default_paths = array(); @@ -171,7 +171,7 @@ final class PhabricatorOwnersEditController : '/owners/'; $form = id(new AphrontFormView()) - ->setUser($user) + ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) @@ -256,24 +256,14 @@ final class PhabricatorOwnersEditController $crumbs->addTextCrumb(pht('New Package')); } - $nav = $this->buildSideNavView(); - $nav->appendChild($crumbs); - $nav->appendChild($form_box); - return $this->buildApplicationPage( array( - $nav, + $crumbs, + $form_box, ), array( 'title' => $title, )); } - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - if ($this->id) { - $view->addFilter('edit/'.$this->id, pht('Edit')); - } else { - $view->addFilter('new', pht('New')); - } - } } diff --git a/src/applications/owners/controller/PhabricatorOwnersListController.php b/src/applications/owners/controller/PhabricatorOwnersListController.php index 2a206616e6..7beb842c82 100644 --- a/src/applications/owners/controller/PhabricatorOwnersListController.php +++ b/src/applications/owners/controller/PhabricatorOwnersListController.php @@ -3,341 +3,52 @@ final class PhabricatorOwnersListController extends PhabricatorOwnersController { - protected $view; - - public function willProcessRequest(array $data) { - $this->view = idx($data, 'view', 'owned'); - $this->setSideNavFilter('view/'.$this->view); + public function shouldAllowPublic() { + return true; } - public function processRequest() { + public function handleRequest(AphrontRequest $request) { + $controller = id(new PhabricatorApplicationSearchController()) + ->setQueryKey($request->getURIData('queryKey')) + ->setSearchEngine(new PhabricatorOwnersPackageSearchEngine()) + ->setNavigation($this->buildSideNavView()); - $request = $this->getRequest(); - $user = $request->getUser(); - - $package = new PhabricatorOwnersPackage(); - $owner = new PhabricatorOwnersOwner(); - $path = new PhabricatorOwnersPath(); - - $repository_phid = ''; - if ($request->getStr('repository') != '') { - $repository_phid = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->withCallsigns(array($request->getStr('repository'))) - ->executeOne() - ->getPHID(); - } - - switch ($this->view) { - case 'search': - $packages = array(); - - $conn_r = $package->establishConnection('r'); - - $where = array('1 = 1'); - $join = array(); - $having = ''; - - if ($request->getStr('name')) { - $where[] = qsprintf( - $conn_r, - 'p.name LIKE %~', - $request->getStr('name')); - } - - if ($repository_phid || $request->getStr('path')) { - - $join[] = qsprintf( - $conn_r, - 'JOIN %T path ON path.packageID = p.id', - $path->getTableName()); - - if ($repository_phid) { - $where[] = qsprintf( - $conn_r, - 'path.repositoryPHID = %s', - $repository_phid); - } - - if ($request->getStr('path')) { - $where[] = qsprintf( - $conn_r, - '(path.path LIKE %~ AND NOT path.excluded) OR - %s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)', - $request->getStr('path'), - $request->getStr('path'), - '_', - '\_', - '%'); - $having = 'HAVING MAX(path.excluded) = 0'; - } - - } - - if ($request->getArr('owner')) { - $join[] = qsprintf( - $conn_r, - 'JOIN %T o ON o.packageID = p.id', - $owner->getTableName()); - $where[] = qsprintf( - $conn_r, - 'o.userPHID IN (%Ls)', - $request->getArr('owner')); - } - - $data = queryfx_all( - $conn_r, - 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q', - $package->getTableName(), - implode(' ', $join), - '('.implode(') AND (', $where).')', - $having); - $packages = $package->loadAllFromArray($data); - - $header = pht('Search Results'); - $nodata = pht('No packages match your query.'); - break; - case 'owned': - $data = queryfx_all( - $package->establishConnection('r'), - 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID - WHERE o.userPHID = %s GROUP BY p.id', - $package->getTableName(), - $owner->getTableName(), - $user->getPHID()); - $packages = $package->loadAllFromArray($data); - - $header = pht('Owned Packages'); - $nodata = pht('No owned packages'); - break; - case 'projects': - $projects = id(new PhabricatorProjectQuery()) - ->setViewer($user) - ->withMemberPHIDs(array($user->getPHID())) - ->withStatus(PhabricatorProjectQuery::STATUS_ANY) - ->execute(); - $owner_phids = mpull($projects, 'getPHID'); - if ($owner_phids) { - $data = queryfx_all( - $package->establishConnection('r'), - 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID - WHERE o.userPHID IN (%Ls) GROUP BY p.id', - $package->getTableName(), - $owner->getTableName(), - $owner_phids); - } else { - $data = array(); - } - $packages = $package->loadAllFromArray($data); - - $header = pht('Project Packages'); - $nodata = pht('No owned packages'); - break; - case 'all': - $packages = $package->loadAll(); - - $header = pht('All Packages'); - $nodata = pht('There are no defined packages.'); - break; - } - - $content = $this->renderPackageTable( - $packages, - $header, - $nodata); - - $filter = new AphrontListFilterView(); - - $owner_phids = $request->getArr('owner'); - - $callsigns = array('' => pht('(Any Repository)')); - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($user) - ->setOrder('callsign') - ->execute(); - foreach ($repositories as $repository) { - $callsigns[$repository->getCallsign()] = - $repository->getCallsign().': '.$repository->getName(); - } - - $form = id(new AphrontFormView()) - ->setUser($user) - ->setAction('/owners/view/search/') - ->setMethod('GET') - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($request->getStr('name'))) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorProjectOrUserDatasource()) - ->setLimit(1) - ->setName('owner') - ->setLabel(pht('Owner')) - ->setValue($owner_phids)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setName('repository') - ->setLabel(pht('Repository')) - ->setOptions($callsigns) - ->setValue($request->getStr('repository'))) - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('path') - ->setLabel(pht('Path')) - ->setValue($request->getStr('path'))) - ->appendChild( - id(new AphrontFormSubmitControl()) - ->setValue(pht('Search for Packages'))); - - $filter->appendChild($form); - $title = pht('Package Index'); - - $crumbs = $this->buildApplicationCrumbs(); - $crumbs->addTextCrumb($header); - $crumbs->setBorder(true); - - $nav = $this->buildSideNavView(); - $nav->appendChild($crumbs); - $nav->appendChild($filter); - $nav->appendChild($content); - - return $this->buildApplicationPage( - $nav, - array( - 'title' => pht('Package Index'), - )); + return $this->delegateToController($controller); } - private function renderPackageTable(array $packages, $header, $nodata) { - assert_instances_of($packages, 'PhabricatorOwnersPackage'); + public function buildSideNavView($for_app = false) { + $viewer = $this->getViewer(); - if ($packages) { - $package_ids = mpull($packages, 'getID'); + $nav = new AphrontSideNavFilterView(); + $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( - 'packageID IN (%Ld)', - $package_ids); - - $paths = id(new PhabricatorOwnersPath())->loadAllWhere( - 'packageID in (%Ld)', - $package_ids); - - $phids = array(); - foreach ($owners as $owner) { - $phids[$owner->getUserPHID()] = true; - } - $phids = array_keys($phids); - $handles = $this->loadViewerHandles($phids); - - $repository_phids = array(); - foreach ($paths as $path) { - $repository_phids[$path->getRepositoryPHID()] = true; - } - - if ($repository_phids) { - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($this->getRequest()->getUser()) - ->withPHIDs(array_keys($repository_phids)) - ->execute(); - } else { - $repositories = array(); - } - - $repositories = mpull($repositories, null, 'getPHID'); - $owners = mgroup($owners, 'getPackageID'); - $paths = mgroup($paths, 'getPackageID'); - } else { - $handles = array(); - $repositories = array(); - $owners = array(); - $paths = array(); + if ($for_app) { + $nav->addFilter('new/', pht('Create Package')); } - $rows = array(); - foreach ($packages as $package) { + id(new PhabricatorOwnersPackageSearchEngine()) + ->setViewer($viewer) + ->addNavigationItems($nav->getMenu()); - $pkg_owners = idx($owners, $package->getID(), array()); - foreach ($pkg_owners as $key => $owner) { - $pkg_owners[$key] = $handles[$owner->getUserPHID()]->renderLink(); - if ($owner->getUserPHID() == $package->getPrimaryOwnerPHID()) { - $pkg_owners[$key] = phutil_tag('strong', array(), $pkg_owners[$key]); - } - } - $pkg_owners = phutil_implode_html(phutil_tag('br'), $pkg_owners); + $nav->selectFilter(null); - $pkg_paths = idx($paths, $package->getID(), array()); - foreach ($pkg_paths as $key => $path) { - $repo = idx($repositories, $path->getRepositoryPHID()); - if ($repo) { - $href = DiffusionRequest::generateDiffusionURI( - array( - 'callsign' => $repo->getCallsign(), - 'branch' => $repo->getDefaultBranch(), - 'path' => $path->getPath(), - 'action' => 'browse', - )); - $pkg_paths[$key] = hsprintf( - '%s %s%s', - ($path->getExcluded() ? "\xE2\x80\x93" : '+'), - phutil_tag('strong', array(), $repo->getName()), - phutil_tag( - 'a', - array( - 'href' => (string)$href, - ), - $path->getPath())); - } else { - $pkg_paths[$key] = $path->getPath(); - } - } - $pkg_paths = phutil_implode_html(phutil_tag('br'), $pkg_paths); - - $rows[] = array( - phutil_tag( - 'a', - array( - 'href' => '/owners/package/'.$package->getID().'/', - ), - $package->getName()), - $pkg_owners, - $pkg_paths, - phutil_tag( - 'a', - array( - 'href' => '/audit/?auditorPHIDs='.$package->getPHID(), - ), - pht('Related Commits')), - ); - } - - $table = new AphrontTableView($rows); - $table->setHeaders( - array( - pht('Name'), - pht('Owners'), - pht('Paths'), - pht('Related Commits'), - )); - $table->setColumnClasses( - array( - 'pri', - '', - 'wide wrap', - 'narrow', - )); - - $panel = new PHUIObjectBoxView(); - $panel->setHeaderText($header); - $panel->appendChild($table); - - return $panel; + return $nav; } - protected function getExtraPackageViews(AphrontSideNavFilterView $view) { - if ($this->view == 'search') { - $view->addFilter('view/search', pht('Search Results')); - } + public function buildApplicationMenu() { + return $this->buildSideNavView(true)->getMenu(); } + + protected function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Create Package')) + ->setHref($this->getApplicationURI('new/')) + ->setIcon('fa-plus-square')); + + return $crumbs; + } + } diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php index 3bbb3b8fe2..64ed2f4098 100644 --- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php +++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php @@ -3,8 +3,10 @@ final class PhabricatorOwnersPackageQuery extends PhabricatorCursorPagedPolicyAwareQuery { + private $ids; private $phids; private $ownerPHIDs; + private $repositoryPHIDs; /** * Owners are direct owners, and members of owning projects. @@ -19,6 +21,16 @@ final class PhabricatorOwnersPackageQuery return $this; } + public function withIDs(array $ids) { + $this->ids = $ids; + return $this; + } + + public function withRepositoryPHIDs(array $phids) { + $this->repositoryPHIDs = $phids; + return $this; + } + protected function loadPage() { $table = new PhabricatorOwnersPackage(); $conn_r = $table->establishConnection('r'); @@ -38,27 +50,48 @@ final class PhabricatorOwnersPackageQuery protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { $joins = array(); - if ($this->ownerPHIDs) { + if ($this->ownerPHIDs !== null) { $joins[] = qsprintf( $conn_r, 'JOIN %T o ON o.packageID = p.id', id(new PhabricatorOwnersOwner())->getTableName()); } + if ($this->repositoryPHIDs !== null) { + $joins[] = qsprintf( + $conn_r, + 'JOIN %T rpath ON rpath.packageID = p.id', + id(new PhabricatorOwnersPath())->getTableName()); + } + return implode(' ', $joins); } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); - if ($this->phids) { + if ($this->phids !== null) { $where[] = qsprintf( $conn_r, 'p.phid IN (%Ls)', $this->phids); } - if ($this->ownerPHIDs) { + if ($this->ids !== null) { + $where[] = qsprintf( + $conn_r, + 'p.id IN (%Ld)', + $this->ids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'rpath.repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + if ($this->ownerPHIDs !== null) { $base_phids = $this->ownerPHIDs; $query = new PhabricatorProjectQuery(); diff --git a/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php new file mode 100644 index 0000000000..f421e3d3ce --- /dev/null +++ b/src/applications/owners/query/PhabricatorOwnersPackageSearchEngine.php @@ -0,0 +1,134 @@ +setParameter( + 'ownerPHIDs', + $this->readUsersFromRequest( + $request, + 'owners', + array( + PhabricatorProjectProjectPHIDType::TYPECONST, + ))); + + $saved->setParameter( + 'repositoryPHIDs', + $this->readPHIDsFromRequest( + $request, + 'repositories', + array( + PhabricatorRepositoryRepositoryPHIDType::TYPECONST, + ))); + + return $saved; + } + + public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + $query = id(new PhabricatorOwnersPackageQuery()); + + $owner_phids = $saved->getParameter('ownerPHIDs', array()); + if ($owner_phids) { + $query->withOwnerPHIDs($owner_phids); + } + + $repository_phids = $saved->getParameter('repositoryPHIDs', array()); + if ($repository_phids) { + $query->withRepositoryPHIDs($repository_phids); + } + + return $query; + } + + public function buildSearchForm( + AphrontFormView $form, + PhabricatorSavedQuery $saved) { + + $owner_phids = $saved->getParameter('ownerPHIDs', array()); + $repository_phids = $saved->getParameter('repositoryPHIDs', array()); + + $form + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new PhabricatorProjectOrUserDatasource()) + ->setName('owners') + ->setLabel(pht('Owners')) + ->setValue($owner_phids)) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new DiffusionRepositoryDatasource()) + ->setName('repositories') + ->setLabel(pht('Repositories')) + ->setValue($repository_phids)); + } + + protected function getURI($path) { + return '/owners/'.$path; + } + + protected function getBuiltinQueryNames() { + $names = array(); + + if ($this->requireViewer()->isLoggedIn()) { + $names['owned'] = pht('Owned'); + } + + $names += array( + 'all' => pht('All Packages'), + ); + + return $names; + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + case 'owned': + return $query->setParameter( + 'ownerPHIDs', + array($this->requireViewer()->getPHID())); + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $packages, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($packages, 'PhabricatorOwnersPackage'); + + $viewer = $this->requireViewer(); + + $list = id(new PHUIObjectItemListView()) + ->setUser($viewer); + foreach ($packages as $package) { + $id = $package->getID(); + + $item = id(new PHUIObjectItemView()) + ->setObject($package) + ->setObjectName(pht('Package %d', $id)) + ->setHeader($package->getName()) + ->setHref('/owners/package/'.$id.'/'); + + $list->addItem($item); + } + + return $list; + } +} diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index f690d3e403..7570a29da7 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -37,7 +37,7 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO return array( // This information is better available from the history table. self::CONFIG_TIMESTAMPS => false, - self::CONFIG_AUX_PHID => true, + self::CONFIG_AUX_PHID => true, self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text128', 'originalName' => 'text255', @@ -60,7 +60,8 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO } public function generatePHID() { - return PhabricatorPHID::generateNewPHID('OPKG'); + return PhabricatorPHID::generateNewPHID( + PhabricatorOwnersPackagePHIDType::TYPECONST); } public function attachUnsavedOwners(array $owners) { From 05bd6a1682e9f79df2019f38b923d5205e8d7377 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:29:31 -0700 Subject: [PATCH 37/43] Modernize Owners package view UI elements Summary: Ref T8320. Use modern/standard UI elements to render package details. Test Plan: {F437131} Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8320 Differential Revision: https://secure.phabricator.com/D13025 --- resources/celerity/map.php | 24 +- .../PhabricatorOwnersDetailController.php | 230 +++++++++++------- 2 files changed, 150 insertions(+), 104 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f9ebfb461b..c5630a527b 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '439658b5', + 'core.pkg.css' => '4e7df908', 'core.pkg.js' => '328799d0', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '30602b8c', @@ -134,7 +134,7 @@ return array( 'rsrc/css/phui/phui-document.css' => '94d5dcd8', 'rsrc/css/phui/phui-feed-story.css' => 'c9f3a0b5', 'rsrc/css/phui/phui-fontkit.css' => 'dd8ddf27', - 'rsrc/css/phui/phui-form-view.css' => '79793450', + 'rsrc/css/phui/phui-form-view.css' => '808329f2', 'rsrc/css/phui/phui-form.css' => 'f535f938', 'rsrc/css/phui/phui-header-view.css' => '75aaf372', 'rsrc/css/phui/phui-icon.css' => 'bc766998', @@ -443,7 +443,7 @@ return array( 'rsrc/js/core/behavior-device.js' => 'a205cf28', 'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '6d49590e', 'rsrc/js/core/behavior-error-log.js' => '6882e80a', - 'rsrc/js/core/behavior-fancy-datepicker.js' => '5c0f680f', + 'rsrc/js/core/behavior-fancy-datepicker.js' => '510b5809', 'rsrc/js/core/behavior-file-tree.js' => '88236f00', 'rsrc/js/core/behavior-form.js' => '5c54cbf3', 'rsrc/js/core/behavior-gesture.js' => '3ab51e2c', @@ -576,7 +576,7 @@ return array( 'javelin-behavior-durable-column' => '16c695bf', 'javelin-behavior-error-log' => '6882e80a', 'javelin-behavior-event-all-day' => '38dcf3c8', - 'javelin-behavior-fancy-datepicker' => '5c0f680f', + 'javelin-behavior-fancy-datepicker' => '510b5809', 'javelin-behavior-global-drag-and-drop' => 'c8e57404', 'javelin-behavior-herald-rule-editor' => '7ebaeed3', 'javelin-behavior-high-security-warning' => 'a464fe03', @@ -776,7 +776,7 @@ return array( 'phui-font-icon-base-css' => '3dad2ae3', 'phui-fontkit-css' => 'dd8ddf27', 'phui-form-css' => 'f535f938', - 'phui-form-view-css' => '79793450', + 'phui-form-view-css' => '808329f2', 'phui-header-view-css' => '75aaf372', 'phui-icon-view-css' => 'bc766998', 'phui-image-mask-css' => '5a8b09c8', @@ -1175,6 +1175,13 @@ return array( 'javelin-typeahead-source', 'javelin-util', ), + '510b5809' => array( + 'javelin-behavior', + 'javelin-util', + 'javelin-dom', + 'javelin-stratcom', + 'javelin-vector', + ), '519705ea' => array( 'javelin-install', 'javelin-dom', @@ -1242,13 +1249,6 @@ return array( 'javelin-uri', 'javelin-routable', ), - '5c0f680f' => array( - 'javelin-behavior', - 'javelin-util', - 'javelin-dom', - 'javelin-stratcom', - 'javelin-vector', - ), '5c54cbf3' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 4193f31972..3c88667e9f 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -19,7 +19,6 @@ final class PhabricatorOwnersDetailController } $paths = $package->loadPaths(); - $owners = $package->loadOwners(); $repository_phids = array(); foreach ($paths as $path) { @@ -36,98 +35,18 @@ final class PhabricatorOwnersDetailController $repositories = array(); } - $phids = array(); - foreach ($owners as $owner) { - $phids[$owner->getUserPHID()] = true; - } - $phids = array_keys($phids); + $actions = $this->buildPackageActionView($package); + $properties = $this->buildPackagePropertyView($package); + $properties->setActionList($actions); - $handles = $this->loadViewerHandles($phids); + $header = id(new PHUIHeaderView()) + ->setUser($viewer) + ->setHeader($package->getName()) + ->setPolicyObject($package); - $rows = array(); - - $rows[] = array(pht('Name'), $package->getName()); - $rows[] = array(pht('Description'), $package->getDescription()); - - $primary_owner = null; - $primary_phid = $package->getPrimaryOwnerPHID(); - if ($primary_phid && isset($handles[$primary_phid])) { - $primary_owner = phutil_tag( - 'strong', - array(), - $handles[$primary_phid]->renderLink()); - } - $rows[] = array(pht('Primary Owner'), $primary_owner); - - $owner_links = array(); - foreach ($owners as $owner) { - $owner_links[] = $handles[$owner->getUserPHID()]->renderLink(); - } - $owner_links = phutil_implode_html(phutil_tag('br'), $owner_links); - $rows[] = array(pht('Owners'), $owner_links); - - $rows[] = array( - pht('Auditing'), - $package->getAuditingEnabled() ? - pht('Enabled') : - pht('Disabled'), - ); - - $path_links = array(); - foreach ($paths as $path) { - $repo = idx($repositories, $path->getRepositoryPHID()); - if (!$repo) { - continue; - } - $href = DiffusionRequest::generateDiffusionURI( - array( - 'callsign' => $repo->getCallsign(), - 'branch' => $repo->getDefaultBranch(), - 'path' => $path->getPath(), - 'action' => 'browse', - )); - $repo_name = phutil_tag('strong', array(), $repo->getName()); - $path_link = phutil_tag( - 'a', - array( - 'href' => (string)$href, - ), - $path->getPath()); - $path_links[] = hsprintf( - '%s %s %s', - ($path->getExcluded() ? "\xE2\x80\x93" : '+'), - $repo_name, - $path_link); - } - $path_links = phutil_implode_html(phutil_tag('br'), $path_links); - $rows[] = array(pht('Paths'), $path_links); - - $table = new AphrontTableView($rows); - $table->setColumnClasses( - array( - 'header', - 'wide', - )); - - $panel = new PHUIObjectBoxView(); - $header = new PHUIHeaderView(); - $header->setHeader( - pht('Package Details for "%s"', $package->getName())); - $header->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setHref('/owners/delete/'.$package->getID().'/') - ->addSigil('workflow') - ->setText(pht('Delete Package'))); - - $header->addActionLink( - id(new PHUIButtonView()) - ->setTag('a') - ->setHref('/owners/edit/'.$package->getID().'/') - ->setText(pht('Edit Package'))); - - $panel->setHeader($header); - $panel->appendChild($table); + $panel = id(new PHUIObjectBoxView()) + ->setHeader($header) + ->addPropertyList($properties); $commit_views = array(); @@ -209,11 +128,138 @@ final class PhabricatorOwnersDetailController array( $crumbs, $panel, + $this->renderPathsTable($paths, $repositories), $commit_panels, ), array( - 'title' => pht('Package %s', $package->getName()), + 'title' => $package->getName(), )); } + + private function buildPackagePropertyView(PhabricatorOwnersPackage $package) { + $viewer = $this->getViewer(); + + $view = id(new PHUIPropertyListView()) + ->setUser($viewer); + + $primary_phid = $package->getPrimaryOwnerPHID(); + if ($primary_phid) { + $primary_owner = $viewer->renderHandle($primary_phid); + } else { + $primary_owner = phutil_tag('em', array(), pht('None')); + } + $view->addProperty(pht('Primary Owner'), $primary_owner); + + // TODO: needOwners() this on the Query. + $owners = $package->loadOwners(); + if ($owners) { + $owner_list = $viewer->renderHandleList(mpull($owners, 'getUserPHID')); + } else { + $owner_list = phutil_tag('em', array(), pht('None')); + } + $view->addProperty(pht('Owners'), $owner_list); + + if ($package->getAuditingEnabled()) { + $auditing = pht('Enabled'); + } else { + $auditing = pht('Disabled'); + } + $view->addProperty(pht('Auditing'), $auditing); + + $description = $package->getDescription(); + if (strlen($description)) { + $view->addSectionHeader(pht('Description')); + $view->addTextContent( + $output = PhabricatorMarkupEngine::renderOneObject( + id(new PhabricatorMarkupOneOff())->setContent($description), + 'default', + $viewer)); + } + + return $view; + } + + private function buildPackageActionView(PhabricatorOwnersPackage $package) { + $viewer = $this->getViewer(); + + // TODO: Implement this capability. + $can_edit = true; + + $id = $package->getID(); + $edit_uri = $this->getApplicationURI("/edit/{$id}/"); + $delete_uri = $this->getApplicationURI("/delete/{$id}/"); + + $view = id(new PhabricatorActionListView()) + ->setUser($viewer) + ->setObject($package) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Package')) + ->setIcon('fa-pencil') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($edit_uri)) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Delete Package')) + ->setIcon('fa-times') + ->setDisabled(!$can_edit) + ->setWorkflow(true) + ->setHref($delete_uri)); + + return $view; + } + + private function renderPathsTable(array $paths, array $repositories) { + $viewer = $this->getViewer(); + + $rows = array(); + foreach ($paths as $path) { + $repo = idx($repositories, $path->getRepositoryPHID()); + if (!$repo) { + continue; + } + $href = DiffusionRequest::generateDiffusionURI( + array( + 'callsign' => $repo->getCallsign(), + 'branch' => $repo->getDefaultBranch(), + 'path' => $path->getPath(), + 'action' => 'browse', + )); + + $path_link = phutil_tag( + 'a', + array( + 'href' => (string)$href, + ), + $path->getPath()); + + $rows[] = array( + ($path->getExcluded() ? '-' : '+'), + $repo->getName(), + $path_link, + ); + } + + $table = id(new AphrontTableView($rows)) + ->setHeaders( + array( + null, + pht('Repository'), + pht('Path'), + )) + ->setColumnClasses( + array( + null, + null, + 'wide', + )); + + return id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Paths')) + ->appendChild($table); + + } + } From 009598593f809b8fd04bf0dfcddcfff1856a7655 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:29:50 -0700 Subject: [PATCH 38/43] Separate editing of package data and paths in Owners Summary: Ref T8320. There's currently one enormous form; split it into a general information form (name, description, owners) and a paths form. I think this is a little more manageable from both a UX point of view and from an "I have to convert this to use ApplicationTransactions" point of view. Test Plan: - Edited paths. - Edited non-path information. - Created new packages. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8320 Differential Revision: https://secure.phabricator.com/D13026 --- src/__phutil_library_map__.php | 2 + .../PhabricatorOwnersApplication.php | 1 + .../PhabricatorOwnersDetailController.php | 27 ++- .../PhabricatorOwnersEditController.php | 128 +++----------- .../PhabricatorOwnersPathsController.php | 156 ++++++++++++++++++ 5 files changed, 211 insertions(+), 103 deletions(-) create mode 100644 src/applications/owners/controller/PhabricatorOwnersPathsController.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index faf31ec97b..5631ebdacc 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2181,6 +2181,7 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', + 'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php', 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', 'PhabricatorPHID' => 'applications/phid/storage/PhabricatorPHID.php', 'PhabricatorPHIDConstants' => 'applications/phid/PhabricatorPHIDConstants.php', @@ -5588,6 +5589,7 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', + 'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController', 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPHPASTApplication' => 'PhabricatorApplication', 'PhabricatorPHPConfigSetupCheck' => 'PhabricatorSetupCheck', diff --git a/src/applications/owners/application/PhabricatorOwnersApplication.php b/src/applications/owners/application/PhabricatorOwnersApplication.php index 97b3adf49b..43dd84b041 100644 --- a/src/applications/owners/application/PhabricatorOwnersApplication.php +++ b/src/applications/owners/application/PhabricatorOwnersApplication.php @@ -47,6 +47,7 @@ final class PhabricatorOwnersApplication extends PhabricatorApplication { 'new/' => 'PhabricatorOwnersEditController', 'package/(?P[1-9]\d*)/' => 'PhabricatorOwnersDetailController', 'delete/(?P[1-9]\d*)/' => 'PhabricatorOwnersDeleteController', + 'paths/(?P[1-9]\d*)/' => 'PhabricatorOwnersPathsController', ), ); } diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 3c88667e9f..51f8b1d4e0 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -188,6 +188,7 @@ final class PhabricatorOwnersDetailController $id = $package->getID(); $edit_uri = $this->getApplicationURI("/edit/{$id}/"); + $paths_uri = $this->getApplicationURI("/paths/{$id}/"); $delete_uri = $this->getApplicationURI("/delete/{$id}/"); $view = id(new PhabricatorActionListView()) @@ -200,6 +201,13 @@ final class PhabricatorOwnersDetailController ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) ->setHref($edit_uri)) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Edit Paths')) + ->setIcon('fa-folder-open') + ->setDisabled(!$can_edit) + ->setWorkflow(!$can_edit) + ->setHref($paths_uri)) ->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Package')) @@ -242,6 +250,18 @@ final class PhabricatorOwnersDetailController ); } + $info = null; + if (!$paths) { + $info = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors( + array( + pht( + 'This package does not contain any paths yet. Use '. + '"Edit Paths" to add some.'), + )); + } + $table = id(new AphrontTableView($rows)) ->setHeaders( array( @@ -256,10 +276,15 @@ final class PhabricatorOwnersDetailController 'wide', )); - return id(new PHUIObjectBoxView()) + $box = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Paths')) ->appendChild($table); + if ($info) { + $box->setInfoView($info); + } + + return $box; } } diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 717d8faacd..8bf4ea5595 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -21,9 +21,13 @@ final class PhabricatorOwnersEditController if (!$package) { return new Aphront404Response(); } + + $is_new = false; } else { $package = new PhabricatorOwnersPackage(); $package->setPrimaryOwnerPHID($viewer->getPHID()); + + $is_new = true; } $e_name = true; @@ -51,22 +55,6 @@ final class PhabricatorOwnersEditController } $owners = array_unique($owners); - $paths = $request->getArr('path'); - $repos = $request->getArr('repo'); - $excludes = $request->getArr('exclude'); - - $path_refs = array(); - for ($ii = 0; $ii < count($paths); $ii++) { - if (empty($paths[$ii]) || empty($repos[$ii])) { - continue; - } - $path_refs[] = array( - 'repositoryPHID' => $repos[$ii], - 'path' => $paths[$ii], - 'excluded' => $excludes[$ii], - ); - } - if (!strlen($package->getName())) { $e_name = pht('Required'); $errors[] = pht('Package name is required.'); @@ -81,13 +69,9 @@ final class PhabricatorOwnersEditController $e_primary = null; } - if (!$path_refs) { - $errors[] = pht('Package must include at least one path.'); - } - if (!$errors) { $package->attachUnsavedOwners($owners); - $package->attachUnsavedPaths($path_refs); + $package->attachUnsavedPaths(array()); $package->attachOldAuditingEnabled($old_auditing_enabled); $package->attachOldPrimaryOwnerPHID($old_primary); try { @@ -95,8 +79,15 @@ final class PhabricatorOwnersEditController ->setActor($viewer) ->setPackage($package) ->save(); - return id(new AphrontRedirectResponse()) - ->setURI('/owners/package/'.$package->getID().'/'); + + $id = $package->getID(); + if ($is_new) { + $next_uri = '/owners/paths/'.$id.'/'; + } else { + $next_uri = '/owners/package/'.$id.'/'; + } + + return id(new AphrontRedirectResponse())->setURI($next_uri); } catch (AphrontDuplicateKeyQueryException $ex) { $e_name = pht('Duplicate'); $errors[] = pht('Package name must be unique.'); @@ -105,16 +96,6 @@ final class PhabricatorOwnersEditController } else { $owners = $package->loadOwners(); $owners = mpull($owners, 'getUserPHID'); - - $paths = $package->loadPaths(); - $path_refs = array(); - foreach ($paths as $path) { - $path_refs[] = array( - 'repositoryPHID' => $path->getRepositoryPHID(), - 'path' => $path->getPath(), - 'excluded' => $path->getExcluded(), - ); - } } $primary = $package->getPrimaryOwnerPHID(); @@ -124,52 +105,16 @@ final class PhabricatorOwnersEditController $value_primary_owner = array(); } - if ($package->getID()) { - $title = pht('Edit Package'); - } else { + if ($is_new) { + $cancel_uri = '/owners/'; $title = pht('New Package'); + $button_text = pht('Continue'); + } else { + $cancel_uri = '/owners/package/'.$package->getID().'/'; + $title = pht('Edit Package'); + $button_text = pht('Save Package'); } - $repos = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->execute(); - - $default_paths = array(); - foreach ($repos as $repo) { - $default_path = $repo->getDetail('default-owners-path'); - if ($default_path) { - $default_paths[$repo->getPHID()] = $default_path; - } - } - - $repos = mpull($repos, 'getCallsign', 'getPHID'); - asort($repos); - - $template = new AphrontTypeaheadTemplateView(); - $template = $template->render(); - - Javelin::initBehavior( - 'owners-path-editor', - array( - 'root' => 'path-editor', - 'table' => 'paths', - 'add_button' => 'addpath', - 'repositories' => $repos, - 'input_template' => $template, - 'pathRefs' => $path_refs, - - 'completeURI' => '/diffusion/services/path/complete/', - 'validateURI' => '/diffusion/services/path/validate/', - - 'repositoryDefaultPaths' => $default_paths, - )); - - require_celerity_resource('owners-path-editor-css'); - - $cancel_uri = $package->getID() - ? '/owners/package/'.$package->getID().'/' - : '/owners/'; - $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( @@ -210,30 +155,6 @@ final class PhabricatorOwnersEditController $package->getAuditingEnabled() ? 'enabled' : 'disabled')) - ->appendChild( - id(new PHUIFormInsetView()) - ->setTitle(pht('Paths')) - ->addDivAttributes(array('id' => 'path-editor')) - ->setRightButton(javelin_tag( - 'a', - array( - 'href' => '#', - 'class' => 'button green', - 'sigil' => 'addpath', - 'mustcapture' => true, - ), - pht('Add New Path'))) - ->setDescription( - pht( - 'Specify the files and directories which comprise '. - 'this package.')) - ->setContent(javelin_tag( - 'table', - array( - 'class' => 'owners-path-editor-table', - 'sigil' => 'paths', - ), - ''))) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Description')) @@ -242,7 +163,7 @@ final class PhabricatorOwnersEditController ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) - ->setValue(pht('Save Package'))); + ->setValue($button_text)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) @@ -251,7 +172,10 @@ final class PhabricatorOwnersEditController $crumbs = $this->buildApplicationCrumbs(); if ($package->getID()) { - $crumbs->addTextCrumb(pht('Edit %s', $package->getName())); + $crumbs->addTextCrumb( + $package->getName(), + $this->getApplicationURI('package/'.$package->getID().'/')); + $crumbs->addTextCrumb(pht('Edit')); } else { $crumbs->addTextCrumb(pht('New Package')); } diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php new file mode 100644 index 0000000000..c603c3204c --- /dev/null +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -0,0 +1,156 @@ +getUser(); + + $package = id(new PhabricatorOwnersPackageQuery()) + ->setViewer($viewer) + ->withIDs(array($request->getURIData('id'))) + ->requireCapabilities( + array( + PhabricatorPolicyCapability::CAN_VIEW, + // TODO: Support this capability. + // PhabricatorPolicyCapability::CAN_EDIT, + )) + ->executeOne(); + if (!$package) { + return new Aphront404Response(); + } + + if ($request->isFormPost()) { + $paths = $request->getArr('path'); + $repos = $request->getArr('repo'); + $excludes = $request->getArr('exclude'); + + $path_refs = array(); + for ($ii = 0; $ii < count($paths); $ii++) { + if (empty($paths[$ii]) || empty($repos[$ii])) { + continue; + } + $path_refs[] = array( + 'repositoryPHID' => $repos[$ii], + 'path' => $paths[$ii], + 'excluded' => $excludes[$ii], + ); + } + + $package->attachUnsavedOwners(array()); + $package->attachUnsavedPaths($path_refs); + $package->attachOldAuditingEnabled($package->getAuditingEnabled()); + $package->attachOldPrimaryOwnerPHID($package->getPrimaryOwnerPHID()); + + id(new PhabricatorOwnersPackageEditor()) + ->setActor($viewer) + ->setPackage($package) + ->save(); + + return id(new AphrontRedirectResponse()) + ->setURI('/owners/package/'.$package->getID().'/'); + } else { + $paths = $package->loadPaths(); + $path_refs = array(); + foreach ($paths as $path) { + $path_refs[] = array( + 'repositoryPHID' => $path->getRepositoryPHID(), + 'path' => $path->getPath(), + 'excluded' => $path->getExcluded(), + ); + } + } + + $repos = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->execute(); + + $default_paths = array(); + foreach ($repos as $repo) { + $default_path = $repo->getDetail('default-owners-path'); + if ($default_path) { + $default_paths[$repo->getPHID()] = $default_path; + } + } + + $repos = mpull($repos, 'getCallsign', 'getPHID'); + asort($repos); + + $template = new AphrontTypeaheadTemplateView(); + $template = $template->render(); + + Javelin::initBehavior( + 'owners-path-editor', + array( + 'root' => 'path-editor', + 'table' => 'paths', + 'add_button' => 'addpath', + 'repositories' => $repos, + 'input_template' => $template, + 'pathRefs' => $path_refs, + + 'completeURI' => '/diffusion/services/path/complete/', + 'validateURI' => '/diffusion/services/path/validate/', + + 'repositoryDefaultPaths' => $default_paths, + )); + + require_celerity_resource('owners-path-editor-css'); + + $cancel_uri = '/owners/package/'.$package->getID().'/'; + + $form = id(new AphrontFormView()) + ->setUser($viewer) + ->appendChild( + id(new PHUIFormInsetView()) + ->setTitle(pht('Paths')) + ->addDivAttributes(array('id' => 'path-editor')) + ->setRightButton(javelin_tag( + 'a', + array( + 'href' => '#', + 'class' => 'button green', + 'sigil' => 'addpath', + 'mustcapture' => true, + ), + pht('Add New Path'))) + ->setDescription( + pht( + 'Specify the files and directories which comprise '. + 'this package.')) + ->setContent(javelin_tag( + 'table', + array( + 'class' => 'owners-path-editor-table', + 'sigil' => 'paths', + ), + ''))) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->addCancelButton($cancel_uri) + ->setValue(pht('Save Paths'))); + + $form_box = id(new PHUIObjectBoxView()) + ->setHeaderText(pht('Edit Paths')) + ->setForm($form); + + $crumbs = $this->buildApplicationCrumbs(); + $crumbs->addTextCrumb( + $package->getName(), + $this->getApplicationURI('package/'.$package->getID().'/')); + $crumbs->addTextCrumb(pht('Edit Paths')); + + return $this->buildApplicationPage( + array( + $crumbs, + $form_box, + ), + array( + 'title' => array( + $package->getName(), + pht('Edit Paths'), + ), + )); + } + +} From da9a61fb70dbee432cc42e97ac8a7e69421cbc3f Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:30:08 -0700 Subject: [PATCH 39/43] Use ApplicationTransactions for all non-path edits to Owners packages Summary: Ref T8320. Test Plan: {F437431} Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8320 Differential Revision: https://secure.phabricator.com/D13028 --- .../autopatches/20150526.owners.mailkey.1.sql | 2 + .../autopatches/20150526.owners.mailkey.2.php | 18 ++ .../autopatches/20150526.owners.xaction.sql | 19 ++ src/__phutil_library_map__.php | 7 + .../PhabricatorOwnersDetailController.php | 6 + .../PhabricatorOwnersEditController.php | 148 +++++------ ...bricatorOwnersPackageTransactionEditor.php | 240 ++++++++++++++++++ ...abricatorOwnersPackageTransactionQuery.php | 10 + .../storage/PhabricatorOwnersPackage.php | 46 +++- .../PhabricatorOwnersPackageTransaction.php | 152 +++++++++++ .../PhabricatorUSEnglishTranslation.php | 18 ++ 11 files changed, 593 insertions(+), 73 deletions(-) create mode 100644 resources/sql/autopatches/20150526.owners.mailkey.1.sql create mode 100644 resources/sql/autopatches/20150526.owners.mailkey.2.php create mode 100644 resources/sql/autopatches/20150526.owners.xaction.sql create mode 100644 src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php create mode 100644 src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php create mode 100644 src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php diff --git a/resources/sql/autopatches/20150526.owners.mailkey.1.sql b/resources/sql/autopatches/20150526.owners.mailkey.1.sql new file mode 100644 index 0000000000..6e83129ec3 --- /dev/null +++ b/resources/sql/autopatches/20150526.owners.mailkey.1.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_owners.owners_package + ADD mailKey binary(20) NOT NULL; diff --git a/resources/sql/autopatches/20150526.owners.mailkey.2.php b/resources/sql/autopatches/20150526.owners.mailkey.2.php new file mode 100644 index 0000000000..c6bb5f8266 --- /dev/null +++ b/resources/sql/autopatches/20150526.owners.mailkey.2.php @@ -0,0 +1,18 @@ +establishConnection('w'); +$iterator = new LiskMigrationIterator($table); +foreach ($iterator as $package) { + $id = $package->getID(); + + echo pht('Adding mail key for package %d...', $id); + echo "\n"; + + queryfx( + $conn_w, + 'UPDATE %T SET mailKey = %s WHERE id = %d', + $table->getTableName(), + Filesystem::readRandomCharacters(20), + $id); +} diff --git a/resources/sql/autopatches/20150526.owners.xaction.sql b/resources/sql/autopatches/20150526.owners.xaction.sql new file mode 100644 index 0000000000..f25b3dfdb6 --- /dev/null +++ b/resources/sql/autopatches/20150526.owners.xaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_owners.owners_packagetransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 5631ebdacc..822667a1ec 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2180,6 +2180,9 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', + 'PhabricatorOwnersPackageTransaction' => 'applications/owners/storage/PhabricatorOwnersPackageTransaction.php', + 'PhabricatorOwnersPackageTransactionEditor' => 'applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php', + 'PhabricatorOwnersPackageTransactionQuery' => 'applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php', 'PhabricatorOwnersPath' => 'applications/owners/storage/PhabricatorOwnersPath.php', 'PhabricatorOwnersPathsController' => 'applications/owners/controller/PhabricatorOwnersPathsController.php', 'PhabricatorPHDConfigOptions' => 'applications/config/option/PhabricatorPHDConfigOptions.php', @@ -5581,6 +5584,7 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackage' => array( 'PhabricatorOwnersDAO', 'PhabricatorPolicyInterface', + 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorOwnersPackageEditor' => 'PhabricatorEditor', @@ -5588,6 +5592,9 @@ phutil_register_library_map(array( 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorOwnersPackageTestCase' => 'PhabricatorTestCase', + 'PhabricatorOwnersPackageTransaction' => 'PhabricatorApplicationTransaction', + 'PhabricatorOwnersPackageTransactionEditor' => 'PhabricatorApplicationTransactionEditor', + 'PhabricatorOwnersPackageTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO', 'PhabricatorOwnersPathsController' => 'PhabricatorOwnersController', 'PhabricatorPHDConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index 51f8b1d4e0..b14835c122 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -124,12 +124,18 @@ final class PhabricatorOwnersDetailController $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb($package->getName()); + $timeline = $this->buildTransactionTimeline( + $package, + new PhabricatorOwnersPackageTransactionQuery()); + $timeline->setShouldTerminate(true); + return $this->buildApplicationPage( array( $crumbs, $panel, $this->renderPathsTable($paths, $repositories), $commit_panels, + $timeline, ), array( 'title' => $package->getName(), diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 8bf4ea5595..9f1987814d 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -21,86 +21,93 @@ final class PhabricatorOwnersEditController if (!$package) { return new Aphront404Response(); } - $is_new = false; } else { - $package = new PhabricatorOwnersPackage(); - $package->setPrimaryOwnerPHID($viewer->getPHID()); - + $package = PhabricatorOwnersPackage::initializeNewPackage($viewer); $is_new = true; } $e_name = true; $e_primary = true; + $v_name = $package->getName(); + $v_primary = $package->getPrimaryOwnerPHID(); + // TODO: Pull these off needOwners() on the Query. + $v_owners = mpull($package->loadOwners(), 'getUserPHID'); + $v_auditing = $package->getAuditingEnabled(); + $v_description = $package->getDescription(); + + $errors = array(); - if ($request->isFormPost()) { - $package->setName($request->getStr('name')); - $package->setDescription($request->getStr('description')); - $old_auditing_enabled = $package->getAuditingEnabled(); - $package->setAuditingEnabled( - ($request->getStr('auditing') === 'enabled') - ? 1 - : 0); + $xactions = array(); - $primary = $request->getArr('primary'); - $primary = reset($primary); - $old_primary = $package->getPrimaryOwnerPHID(); - $package->setPrimaryOwnerPHID($primary); + $v_name = $request->getStr('name'); + $v_primary = head($request->getArr('primary')); + $v_owners = $request->getArr('owners'); + $v_auditing = ($request->getStr('auditing') == 'enabled'); + $v_description = $request->getStr('description'); - $owners = $request->getArr('owners'); - if ($primary) { - array_unshift($owners, $primary); - } - $owners = array_unique($owners); - - if (!strlen($package->getName())) { - $e_name = pht('Required'); - $errors[] = pht('Package name is required.'); - } else { - $e_name = null; + if ($v_primary) { + $v_owners[] = $v_primary; + $v_owners = array_unique($v_owners); } - if (!$package->getPrimaryOwnerPHID()) { - $e_primary = pht('Required'); - $errors[] = pht('Package must have a primary owner.'); - } else { - $e_primary = null; - } + $type_name = PhabricatorOwnersPackageTransaction::TYPE_NAME; + $type_primary = PhabricatorOwnersPackageTransaction::TYPE_PRIMARY; + $type_owners = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; + $type_auditing = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; + $type_description = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; - if (!$errors) { - $package->attachUnsavedOwners($owners); - $package->attachUnsavedPaths(array()); - $package->attachOldAuditingEnabled($old_auditing_enabled); - $package->attachOldPrimaryOwnerPHID($old_primary); - try { - id(new PhabricatorOwnersPackageEditor()) - ->setActor($viewer) - ->setPackage($package) - ->save(); + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_name) + ->setNewValue($v_name); - $id = $package->getID(); - if ($is_new) { - $next_uri = '/owners/paths/'.$id.'/'; - } else { - $next_uri = '/owners/package/'.$id.'/'; - } + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_primary) + ->setNewValue($v_primary); - return id(new AphrontRedirectResponse())->setURI($next_uri); - } catch (AphrontDuplicateKeyQueryException $ex) { - $e_name = pht('Duplicate'); - $errors[] = pht('Package name must be unique.'); + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_owners) + ->setNewValue($v_owners); + + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_auditing) + ->setNewValue($v_auditing); + + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_description) + ->setNewValue($v_description); + + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) + ->setActor($viewer) + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true); + + try { + $editor->applyTransactions($package, $xactions); + + $id = $package->getID(); + if ($is_new) { + $next_uri = '/owners/paths/'.$id.'/'; + } else { + $next_uri = '/owners/package/'.$id.'/'; } + + return id(new AphrontRedirectResponse())->setURI($next_uri); + } catch (AphrontDuplicateKeyQueryException $ex) { + $e_name = pht('Duplicate'); + $errors[] = pht('Package name must be unique.'); + } catch (PhabricatorApplicationTransactionValidationException $ex) { + $validation_exception = $ex; + + $e_name = $ex->getShortMessage($type_name); + $e_primary = $ex->getShortMessage($type_primary); } - } else { - $owners = $package->loadOwners(); - $owners = mpull($owners, 'getUserPHID'); } - $primary = $package->getPrimaryOwnerPHID(); - if ($primary) { - $value_primary_owner = array($primary); + if ($v_primary) { + $value_primary_owner = array($v_primary); } else { $value_primary_owner = array(); } @@ -121,7 +128,7 @@ final class PhabricatorOwnersEditController id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') - ->setValue($package->getName()) + ->setValue($v_name) ->setError($e_name)) ->appendControl( id(new AphrontFormTokenizerControl()) @@ -136,7 +143,7 @@ final class PhabricatorOwnersEditController ->setDatasource(new PhabricatorProjectOrUserDatasource()) ->setLabel(pht('Owners')) ->setName('owners') - ->setValue($owners)) + ->setValue($v_owners)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('auditing') @@ -147,19 +154,18 @@ final class PhabricatorOwnersEditController 'this package will be reviewed to make sure an owner '. 'of the package is involved and the commit message has '. 'a valid revision, reviewed by, and author.')) - ->setOptions(array( - 'disabled' => pht('Disabled'), - 'enabled' => pht('Enabled'), - )) - ->setValue( - $package->getAuditingEnabled() - ? 'enabled' - : 'disabled')) + ->setOptions( + array( + 'disabled' => pht('Disabled'), + 'enabled' => pht('Enabled'), + )) + ->setValue(($v_auditing ? 'enabled' : 'disabled'))) ->appendChild( - id(new AphrontFormTextAreaControl()) + id(new PhabricatorRemarkupControl()) + ->setUser($viewer) ->setLabel(pht('Description')) ->setName('description') - ->setValue($package->getDescription())) + ->setValue($v_description)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php new file mode 100644 index 0000000000..4f4c131c8c --- /dev/null +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -0,0 +1,240 @@ +getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + return $object->getName(); + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + return $object->getPrimaryOwnerPHID(); + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + // TODO: needOwners() this on the Query. + $phids = mpull($object->loadOwners(), 'getUserPHID'); + $phids = array_values($phids); + return $phids; + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + return (int)$object->getAuditingEnabled(); + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + return $object->getDescription(); + } + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + return $xaction->getNewValue(); + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + return (int)$xaction->getNewValue(); + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + $phids = $xaction->getNewValue(); + $phids = array_unique($phids); + $phids = array_values($phids); + return $phids; + } + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + $object->setName($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + $object->setPrimaryOwnerPHID($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + $object->setDescription($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + $object->setAuditingEnabled($xaction->getNewValue()); + return; + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + return; + } + + return parent::applyCustomInternalTransaction($object, $xaction); + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: + return; + case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + // TODO: needOwners this + $owners = $object->loadOwners(); + $owners = mpull($owners, null, 'getUserPHID'); + + $rem = array_diff($old, $new); + foreach ($rem as $phid) { + if (isset($owners[$phid])) { + $owners[$phid]->delete(); + unset($owners[$phid]); + } + } + + $add = array_diff($new, $old); + foreach ($add as $phid) { + $owners[$phid] = id(new PhabricatorOwnersOwner()) + ->setPackageID($object->getID()) + ->setUserPHID($phid) + ->save(); + } + + // TODO: Attach owners here + return; + } + + return parent::applyCustomExternalTransaction($object, $xaction); + } + + protected function validateTransaction( + PhabricatorLiskDAO $object, + $type, + array $xactions) { + + $errors = parent::validateTransaction($object, $type, $xactions); + + switch ($type) { + case PhabricatorOwnersPackageTransaction::TYPE_NAME: + $missing = $this->validateIsEmptyTextField( + $object->getName(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Package name is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: + $missing = $this->validateIsEmptyTextField( + $object->getPrimaryOwnerPHID(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Packages must have a primary owner.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + } + break; + } + + return $errors; + } + + protected function extractFilePHIDsFromCustomTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + return array($xaction->getNewValue()); + } + + return parent::extractFilePHIDsFromCustomTransaction($object, $xaction); + } + + protected function shouldSendMail( + PhabricatorLiskDAO $object, + array $xactions) { + return true; + } + + protected function getMailSubjectPrefix() { + return PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix'); + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + return array( + $object->getPrimaryOwnerPHID(), + $this->requireActor()->getPHID(), + ); + } + + protected function getMailCC(PhabricatorLiskDAO $object) { + // TODO: needOwners() this + return mpull($object->loadOwners(), 'getUserPHID'); + } + + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new OwnersPackageReplyHandler()) + ->setMailReceiver($object); + } + + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $name = $object->getName(); + + return id(new PhabricatorMetaMTAMail()) + ->setSubject($name) + ->addHeader('Thread-Topic', $object->getPHID()); + } + + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $body = parent::buildMailBody($object, $xactions); + + $detail_uri = PhabricatorEnv::getProductionURI( + '/owners/package/'.$object->getID().'/'); + + $body->addLinkSection( + pht('PACKAGE DETAIL'), + $detail_uri); + + return $body; + } + +} diff --git a/src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php new file mode 100644 index 0000000000..e0e7c2792f --- /dev/null +++ b/src/applications/owners/query/PhabricatorOwnersPackageTransactionQuery.php @@ -0,0 +1,10 @@ +setAuditingEnabled(0) + ->setPrimaryOwnerPHID($actor->getPHID()); + } + public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, @@ -44,6 +54,7 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO 'description' => 'text', 'primaryOwnerPHID' => 'phid?', 'auditingEnabled' => 'bool', + 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -64,6 +75,14 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO PhabricatorOwnersPackagePHIDType::TYPECONST); } + public function save() { + if (!$this->getMailKey()) { + $this->setMailKey(Filesystem::readRandomCharacters(20)); + } + + return parent::save(); + } + public function attachUnsavedOwners(array $owners) { $this->unsavedOwners = $owners; return $this; @@ -269,4 +288,27 @@ final class PhabricatorOwnersPackage extends PhabricatorOwnersDAO } return $result; } + + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new PhabricatorOwnersPackageTransactionEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorOwnersPackageTransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + } diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php new file mode 100644 index 0000000000..741bbddf37 --- /dev/null +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -0,0 +1,152 @@ +getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_PRIMARY: + if ($old) { + $phids[] = $old; + } + if ($new) { + $phids[] = $new; + } + break; + case self::TYPE_OWNERS: + $add = array_diff($new, $old); + foreach ($add as $phid) { + $phids[] = $phid; + } + $rem = array_diff($old, $new); + foreach ($rem as $phid) { + $phids[] = $phid; + } + break; + } + + return $phids; + } + + public function shouldHide() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($old === null); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + $author_phid = $this->getAuthorPHID(); + + switch ($this->getTransactionType()) { + case self::TYPE_NAME: + if ($old === null) { + return pht( + '%s created this package.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s renamed this package from "%s" to "%s".', + $this->renderHandleLink($author_phid), + $old, + $new); + } + case self::TYPE_PRIMARY: + return pht( + '%s changed the primary owner for this package from %s to %s.', + $this->renderHandleLink($author_phid), + $this->renderHandleLink($old), + $this->renderHandleLink($new)); + case self::TYPE_OWNERS: + $add = array_diff($new, $old); + $rem = array_diff($old, $new); + if ($add && !$rem) { + return pht( + '%s added %s owner(s): %s.', + $this->renderHandleLink($author_phid), + count($add), + $this->renderHandleList($add)); + } else if ($rem && !$add) { + return pht( + '%s removed %s owner(s): %s.', + $this->renderHandleLink($author_phid), + count($rem), + $this->renderHandleList($rem)); + } else { + return pht( + '%s changed %s package owner(s), added %s: %s; removed %s: %s.', + $this->renderHandleLink($author_phid), + count($add) + count($rem), + count($add), + $this->renderHandleList($add), + count($rem), + $this->renderHandleList($rem)); + } + case self::TYPE_AUDITING: + if ($new) { + return pht( + '%s enabled auditing for this package.', + $this->renderHandleLink($author_phid)); + } else { + return pht( + '%s disabled auditing for this package.', + $this->renderHandleLink($author_phid)); + } + case self::TYPE_DESCRIPTION: + return pht( + '%s updated the description for this package.', + $this->renderHandleLink($author_phid)); + } + + return parent::getTitle(); + } + + public function hasChangeDetails() { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + return ($this->getOldValue() !== null); + } + + return parent::hasChangeDetails(); + } + + public function renderChangeDetails(PhabricatorUser $viewer) { + switch ($this->getTransactionType()) { + case self::TYPE_DESCRIPTION: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + return $this->renderTextCorpusChangeDetails( + $viewer, + $old, + $new); + } + + return parent::renderChangeDetails($viewer); + } + +} diff --git a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php index 61dd34fbe2..111e38857f 100644 --- a/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php +++ b/src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php @@ -1078,6 +1078,24 @@ final class PhabricatorUSEnglishTranslation 'Are you absolutely certain you want to destroy these objects?', ), + '%s added %s owner(s): %s.' => array( + array( + '%s added an owner: %3$s.', + '%s added owners: %3$s.', + ), + ), + + '%s removed %s owner(s): %s.' => array( + array( + '%s removed an owner: %3$s.', + '%s removed owners: %3$s.', + ), + ), + + '%s changed %s package owner(s), added %s: %s; removed %s: %s.' => array( + '%s changed package owners, added: %4$s; removed: %6$s.', + ), + ); } From ebb7ca8cbdaea36eb3c0489f9d97c09ed295fe31 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 10:30:26 -0700 Subject: [PATCH 40/43] Convert Owners paths to application transactions Summary: Ref T8320. Fixes T8317. Fixes T2831. Fixes T8073. Fixes T7127. There was a bug with this line: for ($ii = 0; $ii < count($paths); $ii++) { ...because the array may be sparse if there have been deletes, so `count($paths)` might be 3, but the real keys could be `1`, `5` and `6`. I think this was the primary issue behind T7127. The old Editor did a lot of work to try to validate paths. When a path failed to validate, it silently discarded it. This was silly and pointless: it's incredibly bad UX; and it's totally fine if users saves "invalid" paths. This was likely the cause of T8317, and probably the cause of T8073. T2831 I'm less sure about, but I can't reproduce it and I rewrote all the logic so I suspect it's gone. This also records and shows edits, so if stuff does keep happening it should be more clear what's going on. I removed some adjacent stuff: - I removed the ability to delete packages. I'll add "disable" in a future diff, plus `bin/remove destroy`, like other objects. Getting rid of this now let me get rid of all the mail stuff. - I removed "path validation" where packages would try to automatically update in response to commits. This doesn't necessarily make sense in Git/Mercurial, is sketchy, could easily have been the source of T2831, and seems generally complicated and not very valuable. We could maybe restore it some day, but I'd like to get Owners stable before trying to do crazy stuff like that. Test Plan: {F437687} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T8317, T8073, T7127, T2831, T8320 Differential Revision: https://secure.phabricator.com/D13032 --- resources/celerity/map.php | 6 +- src/__phutil_library_map__.php | 13 -- .../PhabricatorOwnersApplication.php | 1 - .../PhabricatorOwnersDeleteController.php | 44 ---- .../PhabricatorOwnersDetailController.php | 10 +- .../PhabricatorOwnersPathsController.php | 51 +++-- .../editor/PhabricatorOwnersPackageEditor.php | 198 ---------------- ...bricatorOwnersPackageTransactionEditor.php | 50 +++++ .../owners/mail/PackageCreateMail.php | 12 - .../owners/mail/PackageDeleteMail.php | 13 -- src/applications/owners/mail/PackageMail.php | 212 ------------------ .../owners/mail/PackageModifyMail.php | 160 ------------- .../storage/PhabricatorOwnersPackage.php | 57 +---- .../PhabricatorOwnersPackageTransaction.php | 59 +++++ .../owners/storage/PhabricatorOwnersPath.php | 48 ++++ .../PhabricatorOwnersPackagePathValidator.php | 108 --------- ...atorRepositoryCommitChangeParserWorker.php | 3 - webroot/rsrc/css/aphront/table-view.css | 10 + 18 files changed, 204 insertions(+), 851 deletions(-) delete mode 100644 src/applications/owners/controller/PhabricatorOwnersDeleteController.php delete mode 100644 src/applications/owners/editor/PhabricatorOwnersPackageEditor.php delete mode 100644 src/applications/owners/mail/PackageCreateMail.php delete mode 100644 src/applications/owners/mail/PackageDeleteMail.php delete mode 100644 src/applications/owners/mail/PackageMail.php delete mode 100644 src/applications/owners/mail/PackageModifyMail.php delete mode 100644 src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php diff --git a/resources/celerity/map.php b/resources/celerity/map.php index c5630a527b..5ccc0b22c6 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -7,7 +7,7 @@ */ return array( 'names' => array( - 'core.pkg.css' => '4e7df908', + 'core.pkg.css' => '97a49e3e', 'core.pkg.js' => '328799d0', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '30602b8c', @@ -26,7 +26,7 @@ return array( 'rsrc/css/aphront/pager-view.css' => '2e3539af', 'rsrc/css/aphront/panel-view.css' => '8427b78d', 'rsrc/css/aphront/phabricator-nav-view.css' => '7aeaf435', - 'rsrc/css/aphront/table-view.css' => '59e2c0f8', + 'rsrc/css/aphront/table-view.css' => '0b4cd283', 'rsrc/css/aphront/tokenizer.css' => '86a13f7f', 'rsrc/css/aphront/tooltip.css' => '7672b60f', 'rsrc/css/aphront/two-column.css' => '16ab3ad2', @@ -489,7 +489,7 @@ return array( 'aphront-multi-column-view-css' => 'fd18389d', 'aphront-pager-view-css' => '2e3539af', 'aphront-panel-view-css' => '8427b78d', - 'aphront-table-view-css' => '59e2c0f8', + 'aphront-table-view-css' => '0b4cd283', 'aphront-tokenizer-control-css' => '86a13f7f', 'aphront-tooltip-css' => '7672b60f', 'aphront-two-column-view-css' => '16ab3ad2', diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 822667a1ec..1b918a08b0 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1230,10 +1230,6 @@ phutil_register_library_map(array( 'PHUITypeaheadExample' => 'applications/uiexample/examples/PHUITypeaheadExample.php', 'PHUIWorkboardView' => 'view/phui/PHUIWorkboardView.php', 'PHUIWorkpanelView' => 'view/phui/PHUIWorkpanelView.php', - 'PackageCreateMail' => 'applications/owners/mail/PackageCreateMail.php', - 'PackageDeleteMail' => 'applications/owners/mail/PackageDeleteMail.php', - 'PackageMail' => 'applications/owners/mail/PackageMail.php', - 'PackageModifyMail' => 'applications/owners/mail/PackageModifyMail.php', 'PassphraseAbstractKey' => 'applications/passphrase/keys/PassphraseAbstractKey.php', 'PassphraseConduitAPIMethod' => 'applications/passphrase/conduit/PassphraseConduitAPIMethod.php', 'PassphraseController' => 'applications/passphrase/controller/PassphraseController.php', @@ -2167,16 +2163,13 @@ phutil_register_library_map(array( 'PhabricatorOwnersConfigOptions' => 'applications/owners/config/PhabricatorOwnersConfigOptions.php', 'PhabricatorOwnersController' => 'applications/owners/controller/PhabricatorOwnersController.php', 'PhabricatorOwnersDAO' => 'applications/owners/storage/PhabricatorOwnersDAO.php', - 'PhabricatorOwnersDeleteController' => 'applications/owners/controller/PhabricatorOwnersDeleteController.php', 'PhabricatorOwnersDetailController' => 'applications/owners/controller/PhabricatorOwnersDetailController.php', 'PhabricatorOwnersEditController' => 'applications/owners/controller/PhabricatorOwnersEditController.php', 'PhabricatorOwnersListController' => 'applications/owners/controller/PhabricatorOwnersListController.php', 'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php', 'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php', 'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php', - 'PhabricatorOwnersPackageEditor' => 'applications/owners/editor/PhabricatorOwnersPackageEditor.php', 'PhabricatorOwnersPackagePHIDType' => 'applications/owners/phid/PhabricatorOwnersPackagePHIDType.php', - 'PhabricatorOwnersPackagePathValidator' => 'applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php', 'PhabricatorOwnersPackageQuery' => 'applications/owners/query/PhabricatorOwnersPackageQuery.php', 'PhabricatorOwnersPackageSearchEngine' => 'applications/owners/query/PhabricatorOwnersPackageSearchEngine.php', 'PhabricatorOwnersPackageTestCase' => 'applications/owners/storage/__tests__/PhabricatorOwnersPackageTestCase.php', @@ -4565,10 +4558,6 @@ phutil_register_library_map(array( 'PHUITypeaheadExample' => 'PhabricatorUIExample', 'PHUIWorkboardView' => 'AphrontTagView', 'PHUIWorkpanelView' => 'AphrontTagView', - 'PackageCreateMail' => 'PackageMail', - 'PackageDeleteMail' => 'PackageMail', - 'PackageMail' => 'PhabricatorMail', - 'PackageModifyMail' => 'PackageMail', 'PassphraseAbstractKey' => 'Phobject', 'PassphraseConduitAPIMethod' => 'ConduitAPIMethod', 'PassphraseController' => 'PhabricatorController', @@ -5576,7 +5565,6 @@ phutil_register_library_map(array( 'PhabricatorOwnersConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorOwnersController' => 'PhabricatorController', 'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO', - 'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController', 'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController', 'PhabricatorOwnersEditController' => 'PhabricatorOwnersController', 'PhabricatorOwnersListController' => 'PhabricatorOwnersController', @@ -5587,7 +5575,6 @@ phutil_register_library_map(array( 'PhabricatorApplicationTransactionInterface', ), 'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource', - 'PhabricatorOwnersPackageEditor' => 'PhabricatorEditor', 'PhabricatorOwnersPackagePHIDType' => 'PhabricatorPHIDType', 'PhabricatorOwnersPackageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorOwnersPackageSearchEngine' => 'PhabricatorApplicationSearchEngine', diff --git a/src/applications/owners/application/PhabricatorOwnersApplication.php b/src/applications/owners/application/PhabricatorOwnersApplication.php index 43dd84b041..3ed14212dc 100644 --- a/src/applications/owners/application/PhabricatorOwnersApplication.php +++ b/src/applications/owners/application/PhabricatorOwnersApplication.php @@ -46,7 +46,6 @@ final class PhabricatorOwnersApplication extends PhabricatorApplication { 'edit/(?P[1-9]\d*)/' => 'PhabricatorOwnersEditController', 'new/' => 'PhabricatorOwnersEditController', 'package/(?P[1-9]\d*)/' => 'PhabricatorOwnersDetailController', - 'delete/(?P[1-9]\d*)/' => 'PhabricatorOwnersDeleteController', 'paths/(?P[1-9]\d*)/' => 'PhabricatorOwnersPathsController', ), ); diff --git a/src/applications/owners/controller/PhabricatorOwnersDeleteController.php b/src/applications/owners/controller/PhabricatorOwnersDeleteController.php deleted file mode 100644 index 7b275b7407..0000000000 --- a/src/applications/owners/controller/PhabricatorOwnersDeleteController.php +++ /dev/null @@ -1,44 +0,0 @@ -id = $data['id']; - } - - public function processRequest() { - $request = $this->getRequest(); - $user = $request->getUser(); - - $package = id(new PhabricatorOwnersPackage())->load($this->id); - if (!$package) { - return new Aphront404Response(); - } - - if ($request->isDialogFormPost()) { - id(new PhabricatorOwnersPackageEditor()) - ->setActor($user) - ->setPackage($package) - ->delete(); - return id(new AphrontRedirectResponse())->setURI('/owners/'); - } - - $text = pht( - 'Are you sure you want to delete the "%s" package? This '. - 'operation can not be undone.', - $package->getName()); - $dialog = id(new AphrontDialogView()) - ->setUser($user) - ->setTitle(pht('Really delete this package?')) - ->appendChild(phutil_tag('p', array(), $text)) - ->addSubmitButton(pht('Delete')) - ->addCancelButton('/owners/package/'.$package->getID().'/') - ->setSubmitURI($request->getRequestURI()); - - return id(new AphrontDialogResponse())->setDialog($dialog); - } - -} diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php index b14835c122..7935031389 100644 --- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php +++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php @@ -195,7 +195,6 @@ final class PhabricatorOwnersDetailController $id = $package->getID(); $edit_uri = $this->getApplicationURI("/edit/{$id}/"); $paths_uri = $this->getApplicationURI("/paths/{$id}/"); - $delete_uri = $this->getApplicationURI("/delete/{$id}/"); $view = id(new PhabricatorActionListView()) ->setUser($viewer) @@ -213,14 +212,7 @@ final class PhabricatorOwnersDetailController ->setIcon('fa-folder-open') ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit) - ->setHref($paths_uri)) - ->addAction( - id(new PhabricatorActionView()) - ->setName(pht('Delete Package')) - ->setIcon('fa-times') - ->setDisabled(!$can_edit) - ->setWorkflow(true) - ->setHref($delete_uri)); + ->setHref($paths_uri)); return $view; } diff --git a/src/applications/owners/controller/PhabricatorOwnersPathsController.php b/src/applications/owners/controller/PhabricatorOwnersPathsController.php index c603c3204c..95df2cb807 100644 --- a/src/applications/owners/controller/PhabricatorOwnersPathsController.php +++ b/src/applications/owners/controller/PhabricatorOwnersPathsController.php @@ -26,39 +26,48 @@ final class PhabricatorOwnersPathsController $excludes = $request->getArr('exclude'); $path_refs = array(); - for ($ii = 0; $ii < count($paths); $ii++) { - if (empty($paths[$ii]) || empty($repos[$ii])) { - continue; + foreach ($paths as $key => $path) { + if (!isset($repos[$key])) { + throw new Exception( + pht( + 'No repository PHID for path "%s"!', + $key)); } + + if (!isset($excludes[$key])) { + throw new Exception( + pht( + 'No exclusion value for path "%s"!', + $key)); + } + $path_refs[] = array( - 'repositoryPHID' => $repos[$ii], - 'path' => $paths[$ii], - 'excluded' => $excludes[$ii], + 'repositoryPHID' => $repos[$key], + 'path' => $path, + 'excluded' => (int)$excludes[$key], ); } - $package->attachUnsavedOwners(array()); - $package->attachUnsavedPaths($path_refs); - $package->attachOldAuditingEnabled($package->getAuditingEnabled()); - $package->attachOldPrimaryOwnerPHID($package->getPrimaryOwnerPHID()); + $type_paths = PhabricatorOwnersPackageTransaction::TYPE_PATHS; - id(new PhabricatorOwnersPackageEditor()) + $xactions = array(); + $xactions[] = id(new PhabricatorOwnersPackageTransaction()) + ->setTransactionType($type_paths) + ->setNewValue($path_refs); + + $editor = id(new PhabricatorOwnersPackageTransactionEditor()) ->setActor($viewer) - ->setPackage($package) - ->save(); + ->setContentSourceFromRequest($request) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true); + + $editor->applyTransactions($package, $xactions); return id(new AphrontRedirectResponse()) ->setURI('/owners/package/'.$package->getID().'/'); } else { $paths = $package->loadPaths(); - $path_refs = array(); - foreach ($paths as $path) { - $path_refs[] = array( - 'repositoryPHID' => $path->getRepositoryPHID(), - 'path' => $path->getPath(), - 'excluded' => $path->getExcluded(), - ); - } + $path_refs = mpull($paths, 'getRef'); } $repos = id(new PhabricatorRepositoryQuery()) diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditor.php deleted file mode 100644 index 160c9039aa..0000000000 --- a/src/applications/owners/editor/PhabricatorOwnersPackageEditor.php +++ /dev/null @@ -1,198 +0,0 @@ -package = $package; - return $this; - } - - public function getPackage() { - return $this->package; - } - - public function save() { - $actor = $this->getActor(); - $package = $this->getPackage(); - $package->attachActorPHID($actor->getPHID()); - - if ($package->getID()) { - $is_new = false; - } else { - $is_new = true; - } - - $package->openTransaction(); - - $ret = $package->save(); - - $add_owners = array(); - $remove_owners = array(); - $all_owners = array(); - if ($package->getUnsavedOwners()) { - $new_owners = array_fill_keys($package->getUnsavedOwners(), true); - $cur_owners = array(); - foreach ($package->loadOwners() as $owner) { - if (empty($new_owners[$owner->getUserPHID()])) { - $remove_owners[$owner->getUserPHID()] = true; - $owner->delete(); - continue; - } - $cur_owners[$owner->getUserPHID()] = true; - } - - $add_owners = array_diff_key($new_owners, $cur_owners); - $all_owners = array_merge( - array($package->getPrimaryOwnerPHID() => true), - $new_owners, - $remove_owners); - foreach ($add_owners as $phid => $ignored) { - $owner = new PhabricatorOwnersOwner(); - $owner->setPackageID($package->getID()); - $owner->setUserPHID($phid); - $owner->save(); - } - $package->attachUnsavedOwners(array()); - } - - $add_paths = array(); - $remove_paths = array(); - $touched_repos = array(); - if ($package->getUnsavedPaths()) { - $new_paths = igroup( - $package->getUnsavedPaths(), - 'repositoryPHID', - 'path'); - $cur_paths = $package->loadPaths(); - foreach ($cur_paths as $key => $path) { - $repository_phid = $path->getRepositoryPHID(); - $new_path = head(idx( - idx($new_paths, $repository_phid, array()), - $path->getPath(), - array())); - $excluded = $path->getExcluded(); - if ($new_path === false || - idx($new_path, 'excluded') != $excluded) { - $touched_repos[$repository_phid] = true; - $remove_paths[$repository_phid][$path->getPath()] = $excluded; - $path->delete(); - unset($cur_paths[$key]); - } - } - - $cur_paths = mgroup($cur_paths, 'getRepositoryPHID', 'getPath'); - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($actor) - ->withPHIDs(array_keys($cur_paths)) - ->execute(); - $repositories = mpull($repositories, null, 'getPHID'); - foreach ($new_paths as $repository_phid => $paths) { - $repository = idx($repositories, $repository_phid); - if (!$repository) { - continue; - } - foreach ($paths as $path => $dicts) { - $path = ltrim($path, '/'); - // build query to validate path - $drequest = DiffusionRequest::newFromDictionary( - array( - 'user' => $actor, - 'repository' => $repository, - 'path' => $path, - )); - $results = DiffusionBrowseResultSet::newFromConduit( - DiffusionQuery::callConduitWithDiffusionRequest( - $actor, - $drequest, - 'diffusion.browsequery', - array( - 'commit' => $drequest->getCommit(), - 'path' => $path, - 'needValidityOnly' => true, - ))); - $valid = $results->isValidResults(); - $is_directory = true; - if (!$valid) { - switch ($results->getReasonForEmptyResultSet()) { - case DiffusionBrowseResultSet::REASON_IS_FILE: - $valid = true; - $is_directory = false; - break; - case DiffusionBrowseResultSet::REASON_IS_EMPTY: - $valid = true; - break; - } - } - if ($is_directory && substr($path, -1) != '/') { - $path .= '/'; - } - if (substr($path, 0, 1) != '/') { - $path = '/'.$path; - } - if (empty($cur_paths[$repository_phid][$path]) && $valid) { - $touched_repos[$repository_phid] = true; - $excluded = idx(reset($dicts), 'excluded', 0); - $add_paths[$repository_phid][$path] = $excluded; - $obj = new PhabricatorOwnersPath(); - $obj->setPackageID($package->getID()); - $obj->setRepositoryPHID($repository_phid); - $obj->setPath($path); - $obj->setExcluded($excluded); - $obj->save(); - } - } - } - $package->attachUnsavedPaths(array()); - } - - $package->saveTransaction(); - - if ($is_new) { - $mail = new PackageCreateMail($package); - } else { - $mail = new PackageModifyMail( - $package, - array_keys($add_owners), - array_keys($remove_owners), - array_keys($all_owners), - array_keys($touched_repos), - $add_paths, - $remove_paths); - } - $mail->setActor($actor); - $mail->send(); - - return $ret; - } - - public function delete() { - $actor = $this->getActor(); - $package = $this->getPackage(); - $package->attachActorPHID($actor->getPHID()); - - $mails = id(new PackageDeleteMail($package)) - ->setActor($actor) - ->prepareMails(); - - $package->openTransaction(); - - foreach ($package->loadOwners() as $owner) { - $owner->delete(); - } - foreach ($package->loadPaths() as $path) { - $path->delete(); - } - $ret = $package->delete(); - - $package->saveTransaction(); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - - return $ret; - } - -} diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php index 4f4c131c8c..8ce1e48c29 100644 --- a/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php +++ b/src/applications/owners/editor/PhabricatorOwnersPackageTransactionEditor.php @@ -19,6 +19,7 @@ final class PhabricatorOwnersPackageTransactionEditor $types[] = PhabricatorOwnersPackageTransaction::TYPE_OWNERS; $types[] = PhabricatorOwnersPackageTransaction::TYPE_AUDITING; $types[] = PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION; + $types[] = PhabricatorOwnersPackageTransaction::TYPE_PATHS; return $types; } @@ -41,6 +42,10 @@ final class PhabricatorOwnersPackageTransactionEditor return (int)$object->getAuditingEnabled(); case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: return $object->getDescription(); + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + // TODO: needPaths() this on the query + $paths = $object->loadPaths(); + return mpull($paths, 'getRef'); } } @@ -52,6 +57,7 @@ final class PhabricatorOwnersPackageTransactionEditor case PhabricatorOwnersPackageTransaction::TYPE_NAME: case PhabricatorOwnersPackageTransaction::TYPE_PRIMARY: case PhabricatorOwnersPackageTransaction::TYPE_DESCRIPTION: + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: return $xaction->getNewValue(); case PhabricatorOwnersPackageTransaction::TYPE_AUDITING: return (int)$xaction->getNewValue(); @@ -63,6 +69,24 @@ final class PhabricatorOwnersPackageTransactionEditor } } + protected function transactionHasEffect( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + return ($rem || $add); + } + + return parent::transactionHasEffect($object, $xaction); + } + protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { @@ -81,6 +105,7 @@ final class PhabricatorOwnersPackageTransactionEditor $object->setAuditingEnabled($xaction->getNewValue()); return; case PhabricatorOwnersPackageTransaction::TYPE_OWNERS: + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: return; } @@ -122,6 +147,31 @@ final class PhabricatorOwnersPackageTransactionEditor } // TODO: Attach owners here + return; + case PhabricatorOwnersPackageTransaction::TYPE_PATHS: + $old = $xaction->getOldValue(); + $new = $xaction->getNewValue(); + + // TODO: needPaths this + $paths = $object->loadPaths(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + $set = PhabricatorOwnersPath::getSetFromTransactionValue($rem); + foreach ($paths as $path) { + $ref = $path->getRef(); + if (PhabricatorOwnersPath::isRefInSet($ref, $set)) { + $path->delete(); + } + } + + foreach ($add as $ref) { + $path = PhabricatorOwnersPath::newFromRef($ref) + ->setPackageID($object->getID()) + ->save(); + } + return; } diff --git a/src/applications/owners/mail/PackageCreateMail.php b/src/applications/owners/mail/PackageCreateMail.php deleted file mode 100644 index 7f447d7d88..0000000000 --- a/src/applications/owners/mail/PackageCreateMail.php +++ /dev/null @@ -1,12 +0,0 @@ -package = $package; - } - - abstract protected function getVerb(); - - abstract protected function isNewThread(); - - final protected function getPackage() { - return $this->package; - } - - final protected function getHandles() { - return $this->handles; - } - - final protected function getOwners() { - return $this->owners; - } - - final protected function getPaths() { - return $this->paths; - } - - final protected function getMailTo() { - return $this->mailTo; - } - - final protected function renderPackageTitle() { - return $this->getPackage()->getName(); - } - - final protected function renderRepoSubSection($repository_phid, $paths) { - $handles = $this->getHandles(); - $section = array(); - $section[] = ' '. - pht('In repository %s', $handles[$repository_phid]->getName()). - ' - '.PhabricatorEnv::getProductionURI($handles[$repository_phid] - ->getURI()); - foreach ($paths as $path => $excluded) { - $section[] = ' '. - ($excluded ? pht('Excluded') : pht('Included')).' '.$path; - } - - return implode("\n", $section); - } - - protected function needSend() { - return true; - } - - protected function loadData() { - $package = $this->getPackage(); - $owners = $package->loadOwners(); - $this->owners = $owners; - - $owner_phids = mpull($owners, 'getUserPHID'); - $primary_owner_phid = $package->getPrimaryOwnerPHID(); - $mail_to = $owner_phids; - if (!in_array($primary_owner_phid, $owner_phids)) { - $mail_to[] = $primary_owner_phid; - } - $this->mailTo = $mail_to; - - $this->paths = array(); - $repository_paths = mgroup($package->loadPaths(), 'getRepositoryPHID'); - foreach ($repository_paths as $repository_phid => $paths) { - $this->paths[$repository_phid] = mpull($paths, 'getExcluded', 'getPath'); - } - - $phids = array_merge( - $this->mailTo, - array($package->getActorPHID()), - array_keys($this->paths)); - $this->handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($phids) - ->execute(); - } - - final protected function renderSummarySection() { - $package = $this->getPackage(); - $handles = $this->getHandles(); - $section = array(); - $section[] = $handles[$package->getActorPHID()]->getName().' '. - strtolower($this->getVerb()).' '.$this->renderPackageTitle().'.'; - $section[] = ''; - - $section[] = pht('PACKAGE DETAIL'); - $section[] = ' '.PhabricatorEnv::getProductionURI( - '/owners/package/'.$package->getID().'/'); - - return implode("\n", $section); - } - - protected function renderDescriptionSection() { - return pht('PACKAGE DESCRIPTION')."\n ". - $this->getPackage()->getDescription(); - } - - protected function renderPrimaryOwnerSection() { - $handles = $this->getHandles(); - return pht('PRIMARY OWNER')."\n ". - $handles[$this->getPackage()->getPrimaryOwnerPHID()]->getName(); - } - - protected function renderOwnersSection() { - $handles = $this->getHandles(); - $owners = $this->getOwners(); - if (!$owners) { - return null; - } - - $owners = mpull($owners, 'getUserPHID'); - $owners = array_select_keys($handles, $owners); - $owners = mpull($owners, 'getName'); - return pht('OWNERS')."\n ".implode(', ', $owners); - } - - protected function renderAuditingEnabledSection() { - return pht('AUDITING ENABLED STATUS')."\n ". - ($this->getPackage()->getAuditingEnabled() - ? pht('Enabled') - : pht('Disabled')); - } - - protected function renderPathsSection() { - $section = array(); - $section[] = pht('PATHS'); - foreach ($this->paths as $repository_phid => $paths) { - $section[] = $this->renderRepoSubSection($repository_phid, $paths); - } - - return implode("\n", $section); - } - - final protected function renderBody() { - $body = array(); - $body[] = $this->renderSummarySection(); - $body[] = $this->renderDescriptionSection(); - $body[] = $this->renderPrimaryOwnerSection(); - $body[] = $this->renderOwnersSection(); - $body[] = $this->renderAuditingEnabledSection(); - $body[] = $this->renderPathsSection(); - $body = array_filter($body); - return implode("\n\n", $body)."\n"; - } - - final public function send() { - $mails = $this->prepareMails(); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - } - - final public function prepareMails() { - if (!$this->needSend()) { - return array(); - } - - $this->loadData(); - - $package = $this->getPackage(); - $prefix = PhabricatorEnv::getEnvConfig('metamta.package.subject-prefix'); - $verb = $this->getVerb(); - $threading = $this->getMailThreading(); - list($thread_id, $thread_topic) = $threading; - - $template = id(new PhabricatorMetaMTAMail()) - ->setSubject($this->renderPackageTitle()) - ->setSubjectPrefix($prefix) - ->setVarySubjectPrefix("[{$verb}]") - ->setFrom($package->getActorPHID()) - ->setThreadID($thread_id, $this->isNewThread()) - ->addHeader('Thread-Topic', $thread_topic) - ->setRelatedPHID($package->getPHID()) - ->setIsBulk(true) - ->setBody($this->renderBody()); - - $reply_handler = $this->newReplyHandler(); - $mails = $reply_handler->multiplexMail( - $template, - array_select_keys($this->getHandles(), $this->getMailTo()), - array()); - return $mails; - } - - private function getMailThreading() { - return array( - 'package-'.$this->getPackage()->getPHID(), - 'Package '.$this->getPackage()->getOriginalName(), - ); - } - - private function newReplyHandler() { - $reply_handler = new OwnersPackageReplyHandler(); - $reply_handler->setMailReceiver($this->getPackage()); - return $reply_handler; - } - -} diff --git a/src/applications/owners/mail/PackageModifyMail.php b/src/applications/owners/mail/PackageModifyMail.php deleted file mode 100644 index cd96fb0c70..0000000000 --- a/src/applications/owners/mail/PackageModifyMail.php +++ /dev/null @@ -1,160 +0,0 @@ -package = $package; - - $this->addOwners = $add_owners; - $this->removeOwners = $remove_owners; - $this->allOwners = $all_owners; - $this->touchedRepos = $touched_repos; - $this->addPaths = $add_paths; - $this->removePaths = $remove_paths; - } - - protected function getVerb() { - return pht('Modified'); - } - - protected function isNewThread() { - return false; - } - - protected function needSend() { - $package = $this->getPackage(); - if ($package->getOldPrimaryOwnerPHID() !== $package->getPrimaryOwnerPHID() - || $package->getOldAuditingEnabled() != $package->getAuditingEnabled() - || $this->addOwners - || $this->removeOwners - || $this->addPaths - || $this->removePaths) { - return true; - } else { - return false; - } - } - - protected function loadData() { - $this->mailTo = $this->allOwners; - - $phids = array_merge( - $this->allOwners, - $this->touchedRepos, - array( - $this->getPackage()->getActorPHID(), - )); - $this->handles = id(new PhabricatorHandleQuery()) - ->setViewer($this->getActor()) - ->withPHIDs($phids) - ->execute(); - } - - protected function renderDescriptionSection() { - return null; - } - - protected function renderPrimaryOwnerSection() { - $package = $this->getPackage(); - $handles = $this->getHandles(); - - $old_primary_owner_phid = $package->getOldPrimaryOwnerPHID(); - $primary_owner_phid = $package->getPrimaryOwnerPHID(); - if ($old_primary_owner_phid == $primary_owner_phid) { - return null; - } - - $section = array(); - $section[] = pht('PRIMARY OWNER CHANGE'); - $section[] = ' '.pht('Old owner:').' '. - $handles[$old_primary_owner_phid]->getName(); - $section[] = ' '.pht('New owner:').' '. - $handles[$primary_owner_phid]->getName(); - - return implode("\n", $section); - } - - protected function renderOwnersSection() { - $section = array(); - $add_owners = $this->addOwners; - $remove_owners = $this->removeOwners; - $handles = $this->getHandles(); - - if ($add_owners) { - $add_owners = array_select_keys($handles, $add_owners); - $add_owners = mpull($add_owners, 'getName'); - $section[] = pht('ADDED OWNERS'); - $section[] = ' '.implode(', ', $add_owners); - } - - if ($remove_owners) { - if ($add_owners) { - $section[] = ''; - } - $remove_owners = array_select_keys($handles, $remove_owners); - $remove_owners = mpull($remove_owners, 'getName'); - $section[] = pht('REMOVED OWNERS'); - $section[] = ' '.implode(', ', $remove_owners); - } - - if ($section) { - return implode("\n", $section); - } else { - return null; - } - } - - protected function renderAuditingEnabledSection() { - $package = $this->getPackage(); - $old_auditing_enabled = $package->getOldAuditingEnabled(); - $auditing_enabled = $package->getAuditingEnabled(); - if ($old_auditing_enabled == $auditing_enabled) { - return null; - } - - $section = array(); - $section[] = pht('AUDITING ENABLED STATUS CHANGE'); - $section[] = ' '.pht('Old value:').' '. - ($old_auditing_enabled ? pht('Enabled') : pht('Disabled')); - $section[] = ' '.pht('New value:').' '. - ($auditing_enabled ? pht('Enabled') : pht('Disabled')); - return implode("\n", $section); - } - - protected function renderPathsSection() { - $section = array(); - if ($this->addPaths) { - $section[] = pht('ADDED PATHS'); - foreach ($this->addPaths as $repository_phid => $paths) { - $section[] = $this->renderRepoSubSection($repository_phid, $paths); - } - } - - if ($this->removePaths) { - if ($this->addPaths) { - $section[] = ''; - } - $section[] = pht('REMOVED PATHS'); - foreach ($this->removePaths as $repository_phid => $paths) { - $section[] = $this->renderRepoSubSection($repository_phid, $paths); - } - } - return implode("\n", $section); - } - -} diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php index 66245276d0..ecf1862323 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackage.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php @@ -13,12 +13,6 @@ final class PhabricatorOwnersPackage protected $primaryOwnerPHID; protected $mailKey; - private $unsavedOwners = self::ATTACHABLE; - private $unsavedPaths = self::ATTACHABLE; - private $actorPHID; - private $oldPrimaryOwnerPHID; - private $oldAuditingEnabled; - public static function initializeNewPackage(PhabricatorUser $actor) { return id(new PhabricatorOwnersPackage()) ->setAuditingEnabled(0) @@ -83,51 +77,6 @@ final class PhabricatorOwnersPackage return parent::save(); } - public function attachUnsavedOwners(array $owners) { - $this->unsavedOwners = $owners; - return $this; - } - - public function getUnsavedOwners() { - return $this->assertAttached($this->unsavedOwners); - } - - public function attachUnsavedPaths(array $paths) { - $this->unsavedPaths = $paths; - return $this; - } - - public function getUnsavedPaths() { - return $this->assertAttached($this->unsavedPaths); - } - - public function attachActorPHID($actor_phid) { - $this->actorPHID = $actor_phid; - return $this; - } - - public function getActorPHID() { - return $this->actorPHID; - } - - public function attachOldPrimaryOwnerPHID($old_primary) { - $this->oldPrimaryOwnerPHID = $old_primary; - return $this; - } - - public function getOldPrimaryOwnerPHID() { - return $this->oldPrimaryOwnerPHID; - } - - public function attachOldAuditingEnabled($auditing_enabled) { - $this->oldAuditingEnabled = $auditing_enabled; - return $this; - } - - public function getOldAuditingEnabled() { - return $this->oldAuditingEnabled; - } - public function setName($name) { $this->name = $name; if (!$this->getID()) { @@ -163,15 +112,15 @@ final class PhabricatorOwnersPackage } return self::loadPackagesForPaths($repository, $paths); - } + } - public static function loadOwningPackages($repository, $path) { + public static function loadOwningPackages($repository, $path) { if (empty($path)) { return array(); } return self::loadPackagesForPaths($repository, array($path), 1); - } + } private static function loadPackagesForPaths( PhabricatorRepository $repository, diff --git a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php index 741bbddf37..3686125887 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php +++ b/src/applications/owners/storage/PhabricatorOwnersPackageTransaction.php @@ -8,6 +8,7 @@ final class PhabricatorOwnersPackageTransaction const TYPE_OWNERS = 'owners.owners'; const TYPE_AUDITING = 'owners.auditing'; const TYPE_DESCRIPTION = 'owners.description'; + const TYPE_PATHS = 'owners.paths'; public function getApplicationName() { return 'owners'; @@ -120,6 +121,11 @@ final class PhabricatorOwnersPackageTransaction return pht( '%s updated the description for this package.', $this->renderHandleLink($author_phid)); + case self::TYPE_PATHS: + // TODO: Flesh this out. + return pht( + '%s updated paths for this package.', + $this->renderHandleLink($author_phid)); } return parent::getTitle(); @@ -129,6 +135,8 @@ final class PhabricatorOwnersPackageTransaction switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return ($this->getOldValue() !== null); + case self::TYPE_PATHS: + return true; } return parent::hasChangeDetails(); @@ -144,6 +152,57 @@ final class PhabricatorOwnersPackageTransaction $viewer, $old, $new); + case self::TYPE_PATHS: + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + $diffs = PhabricatorOwnersPath::getTransactionValueChanges($old, $new); + list($rem, $add) = $diffs; + + $rows = array(); + foreach ($rem as $ref) { + $rows[] = array( + 'class' => 'diff-removed', + 'change' => '-', + ) + $ref; + } + + foreach ($add as $ref) { + $rows[] = array( + 'class' => 'diff-added', + 'change' => '+', + ) + $ref; + } + + $rowc = array(); + foreach ($rows as $key => $row) { + $rowc[] = $row['class']; + $rows[$key] = array( + $row['change'], + $row['excluded'] ? pht('Exclude') : pht('Include'), + $viewer->renderHandle($row['repositoryPHID']), + $row['path'], + ); + } + + $table = id(new AphrontTableView($rows)) + ->setRowClasses($rowc) + ->setHeaders( + array( + null, + pht('Type'), + pht('Repository'), + pht('Path'), + )) + ->setColumnClasses( + array( + null, + null, + null, + 'wide', + )); + + return $table; } return parent::renderChangeDetails($viewer); diff --git a/src/applications/owners/storage/PhabricatorOwnersPath.php b/src/applications/owners/storage/PhabricatorOwnersPath.php index 0cc987e6bb..f65d6052db 100644 --- a/src/applications/owners/storage/PhabricatorOwnersPath.php +++ b/src/applications/owners/storage/PhabricatorOwnersPath.php @@ -22,4 +22,52 @@ final class PhabricatorOwnersPath extends PhabricatorOwnersDAO { ) + parent::getConfiguration(); } + + public static function newFromRef(array $ref) { + $path = new PhabricatorOwnersPath(); + $path->repositoryPHID = $ref['repositoryPHID']; + $path->path = $ref['path']; + $path->excluded = $ref['excluded']; + return $path; + } + + public function getRef() { + return array( + 'repositoryPHID' => $this->getRepositoryPHID(), + 'path' => $this->getPath(), + 'excluded' => (int)$this->getExcluded(), + ); + } + + public static function getTransactionValueChanges(array $old, array $new) { + return array( + self::getTransactionValueDiff($old, $new), + self::getTransactionValueDiff($new, $old), + ); + } + + private static function getTransactionValueDiff(array $u, array $v) { + $set = self::getSetFromTransactionValue($v); + + foreach ($u as $key => $ref) { + if (self::isRefInSet($ref, $set)) { + unset($u[$key]); + } + } + + return $u; + } + + public static function getSetFromTransactionValue(array $v) { + $set = array(); + foreach ($v as $ref) { + $set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']] = true; + } + return $set; + } + + public static function isRefInSet(array $ref, array $set) { + return isset($set[$ref['repositoryPHID']][$ref['path']][$ref['excluded']]); + } + } diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php b/src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php deleted file mode 100644 index 55b34e7fef..0000000000 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorOwnersPackagePathValidator.php +++ /dev/null @@ -1,108 +0,0 @@ -setViewer($actor) - ->withIDs(array($commit->getRepositoryID())) - ->executeOne(); - if (!$repository) { - return; - } - $changes = self::loadDiffusionChangesForCommit( - $repository, - $commit, - $actor); - - if (!$changes) { - return; - } - - $move_map = array(); - foreach ($changes as $change) { - if ($change->getChangeType() == DifferentialChangeType::TYPE_MOVE_HERE) { - $from_path = '/'.$change->getTargetPath(); - $to_path = '/'.$change->getPath(); - if ($change->getFileType() == DifferentialChangeType::FILE_DIRECTORY) { - $to_path = $to_path.'/'; - $from_path = $from_path.'/'; - } - $move_map[$from_path] = $to_path; - } - } - - if ($move_map) { - self::updateAffectedPackages($repository, $move_map); - } - } - - private static function updateAffectedPackages($repository, array $move_map) { - $paths = array_keys($move_map); - if ($paths) { - $packages = PhabricatorOwnersPackage::loadAffectedPackages($repository, - $paths); - foreach ($packages as $package) { - self::updatePackagePaths($package, $move_map); - } - } - } - - private static function updatePackagePaths($package, array $move_map) { - $paths = array_keys($move_map); - $pkg_paths = $package->loadPaths(); - $new_paths = array(); - foreach ($pkg_paths as $pkg_path) { - $path_changed = false; - - foreach ($paths as $old_path) { - if (strncmp($pkg_path->getPath(), $old_path, strlen($old_path)) === 0) { - $new_paths[] = array ( - 'packageID' => $package->getID(), - 'repositoryPHID' => $pkg_path->getRepositoryPHID(), - 'path' => str_replace($pkg_path->getPath(), $old_path, - $move_map[$old_path]), - ); - $path_changed = true; - } - } - - if (!$path_changed) { - $new_paths[] = array ( - 'packageID' => $package->getID(), - 'repositoryPHID' => $pkg_path->getRepositoryPHID(), - 'path' => $pkg_path->getPath(), - ); - } - } - - if ($new_paths) { - $package->attachOldPrimaryOwnerPHID($package->getPrimaryOwnerPHID()); - $package->attachUnsavedPaths($new_paths); - $package->save(); // save the changes and notify the owners. - } - } - - private static function loadDiffusionChangesForCommit( - PhabricatorRepository $repository, - PhabricatorRepositoryCommit $commit, - PhabricatorUser $actor) { - $data = array( - 'user' => $actor, - 'repository' => $repository, - 'commit' => $commit->getCommitIdentifier(), - ); - $drequest = DiffusionRequest::newFromDictionary($data); - $change_query = - DiffusionPathChangeQuery::newFromDiffusionRequest($drequest); - return $change_query->loadChanges(); - } -} diff --git a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php index 92ba0d8c5b..9fcaeb7254 100644 --- a/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php +++ b/src/applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php @@ -96,9 +96,6 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker id(new PhabricatorSearchIndexer()) ->queueDocumentForIndexing($commit->getPHID()); - PhabricatorOwnersPackagePathValidator::updateOwnersPackagePaths( - $commit, - PhabricatorUser::getOmnipotentUser()); if ($this->shouldQueueFollowupTasks()) { $this->queueTask( 'PhabricatorRepositoryCommitOwnersWorker', diff --git a/webroot/rsrc/css/aphront/table-view.css b/webroot/rsrc/css/aphront/table-view.css index d82cc52c64..6a6a873a93 100644 --- a/webroot/rsrc/css/aphront/table-view.css +++ b/webroot/rsrc/css/aphront/table-view.css @@ -211,6 +211,16 @@ span.single-display-line-content { background: #fcf2bb; } +.aphront-table-view tr.diff-removed, +.aphront-table-view tr.alt-diff-removed { + background: {$lightred} +} + +.aphront-table-view tr.diff-added, +.aphront-table-view tr.alt-diff-added { + background: {$lightgreen} +} + .aphront-table-view tr.no-data td { padding: 12px; text-align: center; From 50240eda03e14130d6ada8c01a1a1459583c2f4c Mon Sep 17 00:00:00 2001 From: lkassianik Date: Wed, 27 May 2015 11:11:11 -0700 Subject: [PATCH 41/43] Create button should be a dropdown with public and private options Summary: Ref T8026, Create button should be a dropdown with public and private options Test Plan: Create both a public and private event. Confirm view policies apply correctly. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8026 Differential Revision: https://secure.phabricator.com/D13033 --- .../controller/PhabricatorCalendarController.php | 14 +++++++++++++- .../PhabricatorCalendarEventEditController.php | 5 ++++- .../calendar/storage/PhabricatorCalendarEvent.php | 12 ++++++++++-- src/view/phui/PHUICrumbsView.php | 1 + src/view/phui/PHUIListItemView.php | 12 ++++++++++++ 5 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/applications/calendar/controller/PhabricatorCalendarController.php b/src/applications/calendar/controller/PhabricatorCalendarController.php index 08af8971ea..2e088ecf49 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarController.php @@ -5,11 +5,23 @@ abstract class PhabricatorCalendarController extends PhabricatorController { protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); + $actions = id(new PhabricatorActionListView()) + ->setUser($this->getViewer()) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Create Private Event')) + ->setHref('/calendar/event/create/?mode=private')) + ->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Create Public Event')) + ->setHref('/calendar/event/create/?mode=public')); + $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('Create Event')) ->setHref($this->getApplicationURI().'event/create/') - ->setIcon('fa-plus-square')); + ->setIcon('fa-plus-square') + ->setDropdownMenu($actions)); return $crumbs; } diff --git a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php index cc19f7273f..109aa692bd 100644 --- a/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php +++ b/src/applications/calendar/controller/PhabricatorCalendarEventEditController.php @@ -29,7 +29,10 @@ final class PhabricatorCalendarEventEditController $uri_query = $request->getStr('query'); if ($this->isCreate()) { - $event = PhabricatorCalendarEvent::initializeNewCalendarEvent($viewer); + $mode = $request->getStr('mode'); + $event = PhabricatorCalendarEvent::initializeNewCalendarEvent( + $viewer, + $mode); $create_start_year = $request->getInt('year'); $create_start_month = $request->getInt('month'); diff --git a/src/applications/calendar/storage/PhabricatorCalendarEvent.php b/src/applications/calendar/storage/PhabricatorCalendarEvent.php index 3055e5e4e6..cd829369d8 100644 --- a/src/applications/calendar/storage/PhabricatorCalendarEvent.php +++ b/src/applications/calendar/storage/PhabricatorCalendarEvent.php @@ -28,18 +28,26 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO private $invitees = self::ATTACHABLE; private $appliedViewer; - public static function initializeNewCalendarEvent(PhabricatorUser $actor) { + public static function initializeNewCalendarEvent( + PhabricatorUser $actor, + $mode) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorCalendarApplication')) ->executeOne(); + if ($mode == 'public') { + $view_policy = PhabricatorPolicies::getMostOpenPolicy(); + } else { + $view_policy = $actor->getPHID(); + } + return id(new PhabricatorCalendarEvent()) ->setUserPHID($actor->getPHID()) ->setIsCancelled(0) ->setIsAllDay(0) ->setIcon(self::DEFAULT_ICON) - ->setViewPolicy($actor->getPHID()) + ->setViewPolicy($view_policy) ->setEditPolicy($actor->getPHID()) ->attachInvitees(array()) ->applyViewerTimezone($actor); diff --git a/src/view/phui/PHUICrumbsView.php b/src/view/phui/PHUICrumbsView.php index 95d78a268c..11ec90fafe 100644 --- a/src/view/phui/PHUICrumbsView.php +++ b/src/view/phui/PHUICrumbsView.php @@ -84,6 +84,7 @@ final class PHUICrumbsView extends AphrontView { 'class' => implode(' ', $action_classes), 'sigil' => implode(' ', $action_sigils), 'style' => $action->getStyle(), + 'meta' => $action->getMetadata(), ), array( $icon, diff --git a/src/view/phui/PHUIListItemView.php b/src/view/phui/PHUIListItemView.php index 79eb6c0f44..e2e347306f 100644 --- a/src/view/phui/PHUIListItemView.php +++ b/src/view/phui/PHUIListItemView.php @@ -29,6 +29,18 @@ final class PHUIListItemView extends AphrontTagView { private $aural; private $profileImage; + public function setDropdownMenu(PhabricatorActionListView $actions) { + Javelin::initBehavior('phui-dropdown-menu'); + + $this->addSigil('phui-dropdown-menu'); + $this->setMetadata( + array( + 'items' => $actions, + )); + + return $this; + } + public function setAural($aural) { $this->aural = $aural; return $this; From ce94fe702b54a5c20cd2725c468fb7804f606be6 Mon Sep 17 00:00:00 2001 From: Bob Trahan Date: Wed, 27 May 2015 15:31:02 -0700 Subject: [PATCH 42/43] Conpherence - make sure "Join" action in public room works even with no message text Summary: Fixes T8328. Somewhere along the line we stopped posting to the server with no text. Make sure if the action is join_room that we ping the server even if no text is specified. Test Plan: tried to send an empty message and failed; nothing happened when I clicked. tried to join a room with an empty message and it worked Reviewers: epriestley Reviewed By: epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T8328 Differential Revision: https://secure.phabricator.com/D13041 --- resources/celerity/map.php | 28 +++++++++---------- .../conpherence/ConpherenceThreadManager.js | 13 ++++++++- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/resources/celerity/map.php b/resources/celerity/map.php index 5ccc0b22c6..6776952e2d 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -8,7 +8,7 @@ return array( 'names' => array( 'core.pkg.css' => '97a49e3e', - 'core.pkg.js' => '328799d0', + 'core.pkg.js' => '0e261ea7', 'darkconsole.pkg.js' => 'e7393ebb', 'differential.pkg.css' => '30602b8c', 'differential.pkg.js' => '8c98ce21', @@ -334,7 +334,7 @@ return array( 'rsrc/js/application/calendar/behavior-day-view.js' => '5c46cff2', 'rsrc/js/application/calendar/behavior-event-all-day.js' => '38dcf3c8', 'rsrc/js/application/config/behavior-reorder-fields.js' => 'b6993408', - 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '10246726', + 'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => '01774ab2', 'rsrc/js/application/conpherence/behavior-drag-and-drop-photo.js' => 'cf86d16a', 'rsrc/js/application/conpherence/behavior-durable-column.js' => '16c695bf', 'rsrc/js/application/conpherence/behavior-menu.js' => 'c0348cac', @@ -504,7 +504,7 @@ return array( 'conpherence-menu-css' => 'f389e048', 'conpherence-message-pane-css' => '5bb4b76d', 'conpherence-notification-css' => '919974b6', - 'conpherence-thread-manager' => '10246726', + 'conpherence-thread-manager' => '01774ab2', 'conpherence-transaction-css' => '42a457f6', 'conpherence-update-css' => '1099a660', 'conpherence-widget-pane-css' => '2af42ebe', @@ -827,6 +827,17 @@ return array( 'unhandled-exception-css' => '37d4f9a2', ), 'requires' => array( + '01774ab2' => array( + 'javelin-dom', + 'javelin-util', + 'javelin-stratcom', + 'javelin-install', + 'javelin-aphlict', + 'javelin-workflow', + 'javelin-router', + 'javelin-behavior-device', + 'javelin-vector', + ), '029a133d' => array( 'aphront-dialog-view-css', ), @@ -890,17 +901,6 @@ return array( 'javelin-install', 'javelin-util', ), - 10246726 => array( - 'javelin-dom', - 'javelin-util', - 'javelin-stratcom', - 'javelin-install', - 'javelin-aphlict', - 'javelin-workflow', - 'javelin-router', - 'javelin-behavior-device', - 'javelin-vector', - ), '13c739ea' => array( 'javelin-behavior', 'javelin-stratcom', diff --git a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js index 71c1af139b..0a39863c74 100644 --- a/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js +++ b/webroot/rsrc/js/application/conpherence/ConpherenceThreadManager.js @@ -447,9 +447,20 @@ JX.install('ConpherenceThreadManager', { }, sendMessage: function(form, params) { + var inputs = JX.DOM.scry(form, 'input'); + var block_empty = true; + for (var i = 0; i < inputs.length; i++) { + if (inputs[i].type != 'hidden') { + continue; + } + if (inputs[i].name == 'action' && inputs[i].value == 'join_room') { + block_empty = false; + continue; + } + } // don't bother sending up text if there is nothing to submit var textarea = JX.DOM.find(form, 'textarea'); - if (!textarea.value.length) { + if (block_empty && !textarea.value.length) { return; } params = this._getParams(params); From 5aa4044a9d384c4ef2f8537c7dc05e7cce4a42f2 Mon Sep 17 00:00:00 2001 From: epriestley Date: Wed, 27 May 2015 15:47:18 -0700 Subject: [PATCH 43/43] Fix synthetic (lint) inline comments for comment hiding Summary: These could cause F442758. Test Plan: {F442779} Reviewers: btrahan, joshuaspence Reviewed By: joshuaspence Subscribers: epriestley Differential Revision: https://secure.phabricator.com/D13042 --- .../differential/storage/DifferentialInlineComment.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/applications/differential/storage/DifferentialInlineComment.php b/src/applications/differential/storage/DifferentialInlineComment.php index 979220fc02..4af5ea5681 100644 --- a/src/applications/differential/storage/DifferentialInlineComment.php +++ b/src/applications/differential/storage/DifferentialInlineComment.php @@ -51,10 +51,16 @@ final class DifferentialInlineComment } public function supportsHiding() { + if ($this->getSyntheticAuthor()) { + return false; + } return true; } public function isHidden() { + if (!$this->supportsHiding()) { + return false; + } return $this->proxy->getIsHidden(); }