Page MenuHomePhabricator

Make assignees or setters of a past Due Date on a non-closed task more aware of that task
Open, MediumPublicFeature

Description

Since T191865 we have Due Dates for tasks.

Since T249807 I've been listing Due Dates in the weekly summary email so I can "nag" on them. I'd love not having to do this (because my time, plus I personally might annoy people while some non-individual system annoying people is better for staying friends :P ).

I probably want something™ more visible, e.g. a banner on top of phabricator.wikimedia.org for either the assignee of the non-closed task that has a Due Date in the past, or if no assignee is set for the person who set the Due Date on that non-closed ticket.

A related, though less powerful proposal is T76094: Have tasks with due dates displayed on a calendar.

Event Timeline

Aklapper changed the subtype of this task from "Task" to "Feature Request".
Aklapper triaged this task as Medium priority.Jul 13 2023, 5:50 PM

Hmm, could also be another notification icon in the top bar (please ignore the duplicated deadline date rendering below in the screenshot):

Screenshot From 2025-03-09 20-34-28.png (283×601 px, 31 KB)

That approach (the icon links to your own task list instead of offering some dropdown) would require fixing T335395 first because deadlines are currently not rendered on https://phabricator.wikimedia.org/people/tasks/5/.

WIP if I want to play with this again (WIP because updating the cache value does not work yet (see SELECT * FROM wmfphab_user.user_cache WHERE cacheType = "tasksdue.count";) plus this doesn't include the arc liberate and bin/celerity spiel, plus code probably welcomes some more checks that this custom field exists etc):

1diff --git a/null b/src/applications/people/cache/PhabricatorUserTasksDueCountCacheType.php
2index 0000000000..18c1a3ce45 100644
3--- a/null
4+++ b/src/applications/people/cache/PhabricatorUserTasksDueCountCacheType.php
5@@ -0,0 +0,0 @@ final class PhabricatorUserTasksDueCountCacheType
6+<?php
7+
8+final class PhabricatorUserTasksDueCountCacheType // 696969notify
9+ extends PhabricatorUserCacheType {
10+
11+ const CACHETYPE = 'tasksdue.count';
12+
13+ const KEY_COUNT = 'user.tasksdue.count.v1';
14+
15+ public function getAutoloadKeys() {
16+ return array(
17+ self::KEY_COUNT,
18+ );
19+ }
20+
21+ public function canManageKey($key) {
22+ return ($key === self::KEY_COUNT);
23+ }
24+
25+ public function getValueFromStorage($value) {
26+ return (int)$value;
27+ }
28+
29+ public function newValueForUsers($key, array $users) {
30+ if (!$users) {
31+ return array();
32+ }
33+
34+ $user_phids = mpull($users, 'getPHID');
35+ $open = ManiphestTaskStatus::getOpenStatusConstants();
36+
37+ $assigned_tasks = id(new ManiphestTaskQuery())
38+ ->setViewer(PhabricatorUser::getOmnipotentUser())
39+ ->withOwners($user_phids)
40+ ->withStatuses($open)
41+ ->setLimit(100)
42+ ->execute();
43+
44+ $overdue_tasks = [];
45+ foreach ($assigned_tasks as $key => $task) {
46+ if (is_subclass_of($task, 'PhabricatorCustomFieldInterface')) {
47+ $field_list = PhabricatorCustomField::getObjectFields(
48+ $task,
49+ PhabricatorCustomField::ROLE_VIEW
50+ );
51+ $field_list->setViewer(PhabricatorUser::getOmnipotentUser());
52+ $field_list->readFieldsFromStorage($task);
53+ $fields = $field_list->getFields();
54+ $deadline = idx($fields, 'std:maniphest:deadline.due');
55+ if ($deadline && $deadline->getValueForStorage()) {
56+ $due_date = $deadline->getValueForStorage();
57+ if ($due_date < time()) {
58+ $overdue_tasks = $task;
59+ }
60+ }
61+ }
62+ }
63+ $empty = array_fill_keys($user_phids, 0);
64+ return $overdue_tasks + $empty;
65+ }
66+
67+}
68diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php
69index 7822ebdc7d..18c1a3ce45 100644
70--- a/src/applications/people/storage/PhabricatorUser.php
71+++ b/src/applications/people/storage/PhabricatorUser.php
72@@ -569,6 +569,11 @@ final class PhabricatorUser
73 return $this->requireCacheData($message_key);
74 }
75
76+ public function getTasksDueCount()
77+ $tasksdue_key = PhabricatorUserTasksDueCountCacheType::KEY_COUNT;
78+ return $this->requireCacheData($tasksdue_key);
79+ }
80+
81 public function getRecentBadgeAwards() {
82 $badges_key = PhabricatorUserBadgesCacheType::KEY_BADGES;
83 return $this->requireCacheData($badges_key);
84diff --git a/src/view/page/menu/PhabricatorMainMenuView.php b/src/view/page/menu/PhabricatorMainMenuView.php
85index f0b2bbbe83..2134b8293c 100644
86--- a/src/view/page/menu/PhabricatorMainMenuView.php
87+++ b/src/view/page/menu/PhabricatorMainMenuView.php
88@@ -534,6 +534,65 @@ final class PhabricatorMainMenuView extends AphrontView {
89 '');
90 }
91
92+// WMF T293734 - tasks due BEGIN
93+ $tasksdue_tag = '';
94+ $tasksdue_notification_dropdown = '';
95+ $tasks = $viewer->getTasksDueCount();
96+ $tasks = ['x', 'y', 'z']; // TODO: Just for testing, remove
97+ if ($tasks && $tasks > 0) {
98+ $tasksdue_id = celerity_generate_unique_node_id();
99+ $tasksdue_count_id = celerity_generate_unique_node_id();
100+ $tasksdue_dropdown_id = celerity_generate_unique_node_id();
101+ $tasksdue_count_number = count($tasks);
102+ if ($tasksdue_count_number) {
103+ $aural[] = phutil_tag(
104+ 'a',
105+ array(
106+ 'href' => '/people/tasks/'.$viewer->getID().'/', // TODO: Update URI for upstream T15998
107+ ),
108+ pht(
109+ '%s overdue tasks.',
110+ new PhutilNumber($tasksdue_count_number)));
111+ } else {
112+ $aural[] = pht('No overdue tasks.');
113+ }
114+ $tasksdue_count_tag = phutil_tag(
115+ 'span',
116+ array(
117+ 'id' => $tasksdue_count_id,
118+ 'class' => 'phabricator-main-menu-tasksdue-count',
119+ ),
120+ $tasksdue_count_number);
121+
122+ $tasksdue_icon_tag = javelin_tag(
123+ 'span',
124+ array(
125+ 'class' => 'phabricator-main-menu-tasksdue-icon phui-icon-view '.
126+ 'phui-font-fa fa-calendar',
127+ 'sigil' => 'menu-icon',
128+ ),
129+ '');
130+
131+ if ($tasksdue_count_number) {
132+ $container_classes[] = 'tasksdue-unread';
133+ }
134+
135+ $tasksdue_tag = phutil_tag(
136+ 'a',
137+ array(
138+ 'href' => '/people/tasks/'.$viewer->getID().'/', // TODO: Update URI for upstream T15998
139+ 'class' => implode(' ', $container_classes),
140+ 'id' => $tasksdue_id,
141+ ),
142+ array(
143+ $tasksdue_icon_tag, // what's that?
144+ $tasksdue_count_tag,
145+ ));
146+
147+ }
148+// WMF T293734 - tasks due END
149+
150+
151 // Admin Level Urgent Notification Channel
152 $setup_tag = '';
153 $setup_notification_dropdown = '';
154@@ -692,6 +751,7 @@ final class PhabricatorMainMenuView extends AphrontView {
155 $bubble_tag,
156 $message_tag,
157 $setup_tag,
158+ $tasksdue_tag, // WMF T293734
159 $user_tag,
160 ),
161 $dropdowns,
162diff --git a/webroot/rsrc/css/application/base/main-menu-view.css b/webroot/rsrc/css/application/base/main-menu-view.css
163index c3e8e4267d..648bafe7b4 100644
164--- a/webroot/rsrc/css/application/base/main-menu-view.css
165+++ b/webroot/rsrc/css/application/base/main-menu-view.css
166@@ -417,6 +417,16 @@ a.phabricator-core-user-menu .caret:before {
167 margin-top: 10px;
168 }
169
170+.phui-icon-view.phabricator-main-menu-tasksdue-icon {
171+ color: #ec6666;
172+ font-size: 24px;
173+ margin-top: 2px;
174+ width: 24px;
175+}
176+.phabricator-main-menu-tasksdue-count {
177+ color: #ec6666;
178+ margin-top: 10px;
179+}
180 .device-desktop .alert-notifications.setup-unread:hover .phui-icon-view {
181 color: #ecf36c;
182 }