@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final class DifferentialRevisionSearchEngine
4 extends PhabricatorApplicationSearchEngine {
5
6 public function getResultTypeDescription() {
7 return pht('Differential Revisions');
8 }
9
10 public function getApplicationClassName() {
11 return PhabricatorDifferentialApplication::class;
12 }
13
14 protected function newResultBuckets() {
15 return DifferentialRevisionResultBucket::getAllResultBuckets();
16 }
17
18 public function newQuery() {
19 return id(new DifferentialRevisionQuery())
20 ->needFlags(true)
21 ->needDrafts(true)
22 ->needReviewers(true);
23 }
24
25 protected function buildQueryFromParameters(array $map) {
26 $query = $this->newQuery();
27
28 if ($map['responsiblePHIDs']) {
29 $query->withResponsibleUsers($map['responsiblePHIDs']);
30 }
31
32 if ($map['authorPHIDs']) {
33 $query->withAuthors($map['authorPHIDs']);
34 }
35
36 if ($map['reviewerPHIDs']) {
37 $query->withReviewers($map['reviewerPHIDs']);
38 }
39
40 if ($map['repositoryPHIDs']) {
41 $query->withRepositoryPHIDs($map['repositoryPHIDs']);
42 }
43
44 if ($map['statuses']) {
45 $query->withStatuses($map['statuses']);
46 }
47
48 if ($map['createdStart'] || $map['createdEnd']) {
49 $query->withCreatedEpochBetween(
50 $map['createdStart'],
51 $map['createdEnd']);
52 }
53
54 if ($map['modifiedStart'] || $map['modifiedEnd']) {
55 $query->withUpdatedEpochBetween(
56 $map['modifiedStart'],
57 $map['modifiedEnd']);
58 }
59
60 if ($map['affectedPaths']) {
61 $query->withPaths($map['affectedPaths']);
62 }
63
64 return $query;
65 }
66
67 protected function buildCustomSearchFields() {
68 return array(
69 id(new PhabricatorSearchDatasourceField())
70 ->setLabel(pht('Responsible Users'))
71 ->setKey('responsiblePHIDs')
72 ->setAliases(array('responsiblePHID', 'responsibles', 'responsible'))
73 ->setDatasource(new DifferentialResponsibleDatasource())
74 ->setDescription(
75 pht('Find revisions that a given user is responsible for.')),
76 id(new PhabricatorUsersSearchField())
77 ->setLabel(pht('Authors'))
78 ->setKey('authorPHIDs')
79 ->setAliases(array('author', 'authors', 'authorPHID'))
80 ->setDescription(
81 pht('Find revisions with specific authors.')),
82 id(new PhabricatorSearchDatasourceField())
83 ->setLabel(pht('Reviewers'))
84 ->setKey('reviewerPHIDs')
85 ->setAliases(array('reviewer', 'reviewers', 'reviewerPHID'))
86 ->setDatasource(new DifferentialReviewerFunctionDatasource())
87 ->setDescription(
88 pht('Find revisions with specific reviewers.')),
89 id(new PhabricatorSearchDatasourceField())
90 ->setLabel(pht('Repositories'))
91 ->setKey('repositoryPHIDs')
92 ->setAliases(array('repository', 'repositories', 'repositoryPHID'))
93 ->setDatasource(new DiffusionRepositoryFunctionDatasource())
94 ->setDescription(
95 pht('Find revisions from specific repositories.')),
96 id(new PhabricatorSearchDatasourceField())
97 ->setLabel(pht('Statuses'))
98 ->setKey('statuses')
99 ->setAliases(array('status'))
100 ->setDatasource(new DifferentialRevisionStatusFunctionDatasource())
101 ->setDescription(
102 pht('Find revisions with particular statuses.')),
103 id(new PhabricatorSearchDateField())
104 ->setLabel(pht('Created After'))
105 ->setKey('createdStart')
106 ->setDescription(
107 pht('Find revisions created at or after a particular time.')),
108 id(new PhabricatorSearchDateField())
109 ->setLabel(pht('Created Before'))
110 ->setKey('createdEnd')
111 ->setDescription(
112 pht('Find revisions created at or before a particular time.')),
113 id(new PhabricatorSearchDateField())
114 ->setLabel(pht('Modified After'))
115 ->setKey('modifiedStart')
116 ->setDescription(
117 pht('Find revisions modified at or after a particular time.')),
118 id(new PhabricatorSearchDateField())
119 ->setLabel(pht('Modified Before'))
120 ->setKey('modifiedEnd')
121 ->setDescription(
122 pht('Find revisions modified at or before a particular time.')),
123 id(new PhabricatorSearchStringListField())
124 ->setKey('affectedPaths')
125 ->setLabel(pht('Affected Paths'))
126 ->setDescription(
127 pht('Search for revisions affecting particular paths.'))
128 ->setIsHidden(true),
129 );
130 }
131
132 protected function getURI($path) {
133 return '/differential/'.$path;
134 }
135
136 protected function getBuiltinQueryNames() {
137 $names = array();
138
139 if ($this->requireViewer()->isLoggedIn()) {
140 $names['active'] = pht('Active Revisions');
141 $names['authored'] = pht('Authored');
142 }
143
144 $names['all'] = pht('All Revisions');
145
146 return $names;
147 }
148
149 public function buildSavedQueryFromBuiltin($query_key) {
150 $query = $this->newSavedQuery();
151 $query->setQueryKey($query_key);
152
153 $viewer = $this->requireViewer();
154
155 switch ($query_key) {
156 case 'active':
157 $bucket_key = DifferentialRevisionRequiredActionResultBucket::BUCKETKEY;
158
159 return $query
160 ->setParameter('responsiblePHIDs', array($viewer->getPHID()))
161 ->setParameter('statuses', array('open()'))
162 ->setParameter('bucket', $bucket_key);
163 case 'authored':
164 return $query
165 ->setParameter('authorPHIDs', array($viewer->getPHID()));
166 case 'all':
167 return $query;
168 }
169
170 return parent::buildSavedQueryFromBuiltin($query_key);
171 }
172
173 /**
174 * @param array<DifferentialRevision> $revisions
175 * @param PhabricatorSavedQuery $query
176 * @param array<PhabricatorObjectHandle> $handles
177 */
178 protected function renderResultList(
179 array $revisions,
180 PhabricatorSavedQuery $query,
181 array $handles) {
182 assert_instances_of($revisions, DifferentialRevision::class);
183
184 $viewer = $this->requireViewer();
185 $template = id(new DifferentialRevisionListView())
186 ->setViewer($viewer)
187 ->setNoBox($this->isPanelContext());
188
189 $bucket = $this->getResultBucket($query);
190
191 $unlanded = $this->loadUnlandedDependencies($revisions);
192
193 $custom_field_lists = $this->loadCustomFields(
194 $revisions,
195 PhabricatorCustomField::ROLE_LIST);
196
197 $views = array();
198 if ($bucket) {
199 $bucket->setViewer($viewer);
200
201 try {
202 $groups = $bucket->newResultGroups($query, $revisions);
203
204 foreach ($groups as $group) {
205 // Don't show groups in Dashboard Panels
206 if ($group->getObjects() || !$this->isPanelContext()) {
207 $views[] = id(clone $template)
208 ->setHeader($group->getName())
209 ->setNoDataString($group->getNoDataString())
210 ->setRevisions($group->getObjects());
211 }
212 }
213 } catch (Exception $ex) {
214 $this->addError($ex->getMessage());
215 }
216 } else {
217 $views[] = id(clone $template)
218 ->setRevisions($revisions);
219 }
220
221 if (!$views) {
222 $views[] = id(new DifferentialRevisionListView())
223 ->setViewer($viewer)
224 ->setNoDataString(pht('No revisions found.'));
225 }
226
227 foreach ($views as $view) {
228 $view->setUnlandedDependencies($unlanded);
229 $view->setCustomFieldLists($custom_field_lists);
230 }
231
232 if (count($views) == 1) {
233 // Reduce this to a PHUIObjectItemListView so we can get the free
234 // support from ApplicationSearch.
235 $list = head($views)->render();
236 } else {
237 $list = $views;
238 }
239
240 $result = new PhabricatorApplicationSearchResultView();
241 $result->setContent($list);
242
243 return $result;
244 }
245
246 protected function getNewUserBody() {
247 $create_button = id(new PHUIButtonView())
248 ->setTag('a')
249 ->setText(pht('Create a Diff'))
250 ->setHref('/differential/diff/create/')
251 ->setColor(PHUIButtonView::GREEN);
252
253 $icon = $this->getApplication()->getIcon();
254 $app_name = $this->getApplication()->getName();
255 $view = id(new PHUIBigInfoView())
256 ->setIcon($icon)
257 ->setTitle(pht('Welcome to %s', $app_name))
258 ->setDescription(
259 pht('Pre-commit code review. Revisions that are waiting on your input '.
260 'will appear here.'))
261 ->addAction($create_button);
262
263 return $view;
264 }
265
266 private function loadUnlandedDependencies(array $revisions) {
267 $phids = array();
268 foreach ($revisions as $revision) {
269 if (!$revision->isAccepted()) {
270 continue;
271 }
272
273 $phids[] = $revision->getPHID();
274 }
275
276 if (!$phids) {
277 return array();
278 }
279
280 $query = id(new PhabricatorEdgeQuery())
281 ->withSourcePHIDs($phids)
282 ->withEdgeTypes(
283 array(
284 DifferentialRevisionDependsOnRevisionEdgeType::EDGECONST,
285 ));
286
287 $query->execute();
288
289 $revision_phids = $query->getDestinationPHIDs();
290 if (!$revision_phids) {
291 return array();
292 }
293
294 $viewer = $this->requireViewer();
295
296 $blocking_revisions = id(new DifferentialRevisionQuery())
297 ->setViewer($viewer)
298 ->withPHIDs($revision_phids)
299 ->withIsOpen(true)
300 ->execute();
301 $blocking_revisions = mpull($blocking_revisions, null, 'getPHID');
302
303 $result = array();
304 foreach ($revisions as $revision) {
305 $revision_phid = $revision->getPHID();
306 $blocking_phids = $query->getDestinationPHIDs(array($revision_phid));
307 $blocking = array_select_keys($blocking_revisions, $blocking_phids);
308 if ($blocking) {
309 $result[$revision_phid] = $blocking;
310 }
311 }
312
313 return $result;
314 }
315
316 protected function newExportFields() {
317 $fields = array(
318 id(new PhabricatorStringExportField())
319 ->setKey('monogram')
320 ->setLabel(pht('Monogram')),
321 id(new PhabricatorPHIDExportField())
322 ->setKey('authorPHID')
323 ->setLabel(pht('Author PHID')),
324 id(new PhabricatorStringExportField())
325 ->setKey('author')
326 ->setLabel(pht('Author')),
327 id(new PhabricatorStringExportField())
328 ->setKey('status')
329 ->setLabel(pht('Status')),
330 id(new PhabricatorStringExportField())
331 ->setKey('statusName')
332 ->setLabel(pht('Status Name')),
333 id(new PhabricatorURIExportField())
334 ->setKey('uri')
335 ->setLabel(pht('URI')),
336 id(new PhabricatorStringExportField())
337 ->setKey('title')
338 ->setLabel(pht('Title')),
339 id(new PhabricatorStringExportField())
340 ->setKey('summary')
341 ->setLabel(pht('Summary')),
342 id(new PhabricatorStringExportField())
343 ->setKey('testPlan')
344 ->setLabel(pht('Test Plan')),
345 );
346
347 return $fields;
348 }
349
350 protected function newExportData(array $revisions) {
351 $viewer = $this->requireViewer();
352
353 $phids = array();
354 foreach ($revisions as $revision) {
355 $phids[] = $revision->getAuthorPHID();
356 }
357 $handles = $viewer->loadHandles($phids);
358
359 $export = array();
360 foreach ($revisions as $revision) {
361
362 $author_phid = $revision->getAuthorPHID();
363 if ($author_phid) {
364 $author_name = $handles[$author_phid]->getName();
365 } else {
366 $author_name = null;
367 }
368
369 $status = $revision->getStatusObject();
370 $status_name = $status->getDisplayName();
371 $status_value = $status->getKey();
372
373 $export[] = array(
374 'monogram' => $revision->getMonogram(),
375 'authorPHID' => $author_phid,
376 'author' => $author_name,
377 'status' => $status_value,
378 'statusName' => $status_name,
379 'uri' => PhabricatorEnv::getProductionURI($revision->getURI()),
380 'title' => (string)$revision->getTitle(),
381 'summary' => (string)$revision->getSummary(),
382 'testPlan' => (string)$revision->getTestPlan(),
383 );
384 }
385
386 return $export;
387 }
388
389}