Coverage for portality / bll / services / todo.py: 84%

287 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-04 09:41 +0100

1from __future__ import annotations 

2 

3from portality import constants 

4from portality import models 

5from portality.bll import exceptions 

6from portality.lib import dates 

7from portality.lib.argvalidate import argvalidate 

8 

9 

10class TodoService(object): 

11 """ 

12 ~~Todo:Service->DOAJ:Service~~ 

13 ~~-> ApplicationStatuses:Config~~ 

14 """ 

15 

16 def group_stats(self, group_id): 

17 # ~~-> EditorGroup:Model~~ 

18 eg = models.EditorGroup.pull(group_id) 

19 stats = {"editor_group": eg.data} 

20 

21 # ~~-> Account:Model ~~ 

22 stats["editors"] = {} 

23 editors = [eg.editor] + eg.associates 

24 for editor in editors: 

25 acc = models.Account.pull(editor) 

26 stats["editors"][editor] = { 

27 "email": None if acc is None else acc.email 

28 } 

29 

30 q = GroupStatsQuery(eg.name) 

31 resp = models.Application.query(q=q.query()) 

32 

33 stats["total"] = {"applications": 0, "update_requests": 0} 

34 

35 stats["by_editor"] = {} 

36 for bucket in resp.get("aggregations", {}).get("editor", {}).get("buckets", []): 

37 stats["by_editor"][bucket["key"]] = {"applications": 0, "update_requests": 0} 

38 

39 for b in bucket.get("application_type", {}).get("buckets", []): 

40 if b["key"] == constants.APPLICATION_TYPE_NEW_APPLICATION: 

41 stats["by_editor"][bucket["key"]]["applications"] = b["doc_count"] 

42 stats["total"]["applications"] += b["doc_count"] 

43 elif b["key"] == constants.APPLICATION_TYPE_UPDATE_REQUEST: 

44 stats["by_editor"][bucket["key"]]["update_requests"] = b["doc_count"] 

45 stats["total"]["update_requests"] += b["doc_count"] 

46 

47 unassigned_buckets = resp.get("aggregations", {}).get("unassigned", {}).get( 

48 "application_type", {}).get("buckets", []) 

49 stats["unassigned"] = {"applications": 0, "update_requests": 0} 

50 for ub in unassigned_buckets: 

51 if ub["key"] == constants.APPLICATION_TYPE_NEW_APPLICATION: 

52 stats["unassigned"]["applications"] = ub["doc_count"] 

53 stats["total"]["applications"] += ub["doc_count"] 

54 elif ub["key"] == constants.APPLICATION_TYPE_UPDATE_REQUEST: 

55 stats["unassigned"]["update_requests"] = ub["doc_count"] 

56 stats["total"]["update_requests"] += ub["doc_count"] 

57 

58 stats["by_status"] = {} 

59 for bucket in resp.get("aggregations", {}).get("status", {}).get("buckets", []): 

60 stats["by_status"][bucket["key"]] = {"applications": 0, "update_requests": 0} 

61 for b in bucket.get("application_type", {}).get("buckets", []): 

62 if b["key"] == constants.APPLICATION_TYPE_NEW_APPLICATION: 

63 stats["by_status"][bucket["key"]]["applications"] = b["doc_count"] 

64 elif b["key"] == constants.APPLICATION_TYPE_UPDATE_REQUEST: 

65 stats["by_status"][bucket["key"]]["update_requests"] = b["doc_count"] 

66 

67 stats["historical_numbers"] = self.group_finished_historical_counts(eg) 

68 

69 return stats 

70 

71 def group_finished_historical_counts(self, editor_group: models.EditorGroup, year=None): 

72 """ 

73 Get the count of applications in an editor group where 

74 Associate Editors set to Completed when they have done their review 

75 Editors set them to Ready 

76 in a given year (current by default) 

77 :param editor_group 

78 :param year 

79 :return: historical for editor and associate editor in dict 

80 """ 

81 year_for_query = dates.now_str(dates.FMT_YEAR) if year is None else year 

82 editor_status = "status:" + constants.APPLICATION_STATUS_READY 

83 associate_status = "status:" + constants.APPLICATION_STATUS_COMPLETED 

84 

85 stats = {"year": year_for_query} 

86 

87 hs = HistoricalNumbersQuery(editor_group.editor, editor_status, editor_group.id) 

88 # ~~-> Provenance:Model ~~ 

89 editor_count = models.Provenance.count(query=hs.query()) 

90 

91 # ~~-> Account:Model ~~ 

92 acc = models.Account.pull(editor_group.editor) 

93 if acc is not None: 

94 stats["editor"] = {"id": acc.id, "count": editor_count} 

95 

96 stats["associate_editors"] = [] 

97 for associate in editor_group.associates: 

98 hs = HistoricalNumbersQuery(associate, associate_status, editor_group.id) 

99 associate_count = models.Provenance.count(query=hs.query()) 

100 acc = models.Account.pull(associate) 

101 if acc is not None: 

102 stats["associate_editors"].append({"id": acc.id, "name": acc.name, "count": associate_count}) 

103 

104 return stats 

105 

106 def user_finished_historical_counts(self, account, year=None): 

107 """ 

108 Get the count of overall applications 

109 Associate Editors set to Completed 

110 Editors set them to Ready 

111 in a given year (current by default) 

112 :param account 

113 :param year 

114 :return: 

115 """ 

116 hs = None 

117 

118 if account.has_role("editor"): 

119 hs = HistoricalNumbersQuery(account.id, "status:" + constants.APPLICATION_STATUS_READY, year) 

120 elif account.has_role("associate_editor"): 

121 hs = HistoricalNumbersQuery(account.id, "status:" + constants.APPLICATION_STATUS_COMPLETED, year) 

122 

123 if hs: 

124 count = models.Provenance.count(query=hs.query()) 

125 else: 

126 count = None 

127 

128 return count 

129 

130 def top_todo(self, account, size=25, new_applications=True, update_requests=True, on_hold=True) -> list[dict]: 

131 """ 

132 Returns the top number of todo items for a given user 

133 

134 :param account: 

135 :param size: 

136 :return: 

137 """ 

138 # first validate the incoming arguments to ensure that we've got the right thing 

139 argvalidate("top_todo", [ 

140 {"arg": account, "instance": models.Account, "allow_none": False, "arg_name": "account"} 

141 ], exceptions.ArgumentException) 

142 

143 queries = [] 

144 if account.has_role("admin"): 

145 maned_of = models.EditorGroup.groups_by_maned(account.id) 

146 if new_applications: 

147 queries.append(TodoRules.maned_follow_up_old(size, maned_of)) 

148 queries.append(TodoRules.maned_stalled(size, maned_of)) 

149 queries.append(TodoRules.maned_ready(size, maned_of)) 

150 queries.append(TodoRules.maned_completed(size, maned_of)) 

151 queries.append(TodoRules.maned_assign_pending(size, maned_of)) 

152 #queries.append(TodoRules.urgent_flags_new_applications(account.id, size)) 

153 #queries.append(TodoRules.regular_flags_new_applications(account.id, size)) 

154 if update_requests: 

155 queries.append(TodoRules.maned_last_month_update_requests(size, maned_of)) 

156 queries.append(TodoRules.maned_new_update_requests(size, maned_of)) 

157 #queries.append(TodoRules.urgent_flags_update_requests(account.id, size)) 

158 #queries.append(TodoRules.regular_flags_update_requests(account.id, size)) 

159 if on_hold: 

160 queries.append(TodoRules.maned_on_hold(size, account.id, maned_of)) 

161 #queries.append(TodoRules.urgent_flags_onhold(account.id, size)) 

162 #queries.append(TodoRules.regular_flags_onhold(account.id, size)) 

163 

164 if new_applications: # editor and associate editor roles only deal with new applications 

165 if account.has_role("editor"): 

166 groups = [g for g in models.EditorGroup.groups_by_editor(account.id)] 

167 regular_groups = [g for g in groups if g.maned != account.id] 

168 maned_groups = [g for g in groups if g.maned == account.id] 

169 if len(groups) > 0: 

170 queries.append(TodoRules.editor_follow_up_old(groups, size)) 

171 queries.append(TodoRules.editor_stalled(groups, size)) 

172 queries.append(TodoRules.editor_completed(groups, size)) 

173 

174 # for groups where the user is not the maned for a group, given them the assign 

175 # pending todos at the regular priority 

176 if len(regular_groups) > 0: 

177 queries.append(TodoRules.editor_assign_pending(regular_groups, size)) 

178 

179 # for groups where the user IS the maned for a group, give them the assign 

180 # pending todos at a lower priority 

181 if len(maned_groups) > 0: 

182 qi = TodoRules.editor_assign_pending(maned_groups, size) 

183 queries.append((constants.TODO_EDITOR_ASSIGN_PENDING_LOW_PRIORITY, qi[1], qi[2], -2)) 

184 

185 if account.has_role(constants.ROLE_ASSOCIATE_EDITOR): 

186 queries.extend([ 

187 TodoRules.associate_follow_up_old(account.id, size), 

188 TodoRules.associate_stalled(account.id, size), 

189 TodoRules.associate_start_pending(account.id, size), 

190 TodoRules.associate_all_applications(account.id, size) 

191 ]) 

192 

193 todos = [] 

194 for aid, q, sort, boost in queries: 

195 applications = models.Application.object_query(q=q.query()) 

196 for ap in applications: 

197 sort_map = { 

198 "last_manual_update": ap.last_manual_update_timestamp, 

199 "created_date": ap.date_applied_timestamp, 

200 "most_urgent_flag_deadline": ap.most_urgent_flag_deadline_timestamp 

201 } 

202 todos.append({ 

203 "date": sort_map.get(sort, ap.date_applied_timestamp), 

204 "date_type": sort, 

205 "action_id": [aid], 

206 "title": ap.bibjson().title, 

207 "object_id": ap.id, 

208 "object": ap, 

209 "boost": boost 

210 }) 

211 

212 todos = self._rationalise_todos(todos, size) 

213 

214 return todos 

215 

216 def _rationalise_todos(self, todos, size): 

217 boost_groups = sorted(list(set([x["boost"] for x in todos])), reverse=True) 

218 

219 stds = [] 

220 for bg in boost_groups: 

221 group = list(filter(lambda x: x["boost"] == bg, todos)) 

222 stds += sorted(group, key=lambda x: x['date']) 

223 

224 id_map = {} 

225 removals = [] 

226 for i in range(len(stds)): 

227 todo = stds[i] 

228 oid = todo["object_id"] 

229 if oid in id_map: 

230 removals.append(i) 

231 stds[id_map[oid]]['action_id'] += todo['action_id'] 

232 else: 

233 id_map[oid] = i 

234 

235 removals.reverse() 

236 for r in removals: 

237 del stds[r] 

238 

239 return stds[:size] 

240 

241 

242class TodoRules(object): 

243 

244 @classmethod 

245 def maned_stalled(cls, size, maned_of): 

246 sort_date = "created_date" 

247 stalled = TodoQuery( 

248 musts=[ 

249 TodoQuery.lmu_older_than(8), 

250 TodoQuery.editor_group(maned_of), 

251 TodoQuery.is_new_application() 

252 ], 

253 must_nots=[ 

254 TodoQuery.status([ 

255 constants.APPLICATION_STATUS_ACCEPTED, 

256 constants.APPLICATION_STATUS_REJECTED, 

257 constants.APPLICATION_STATUS_ON_HOLD 

258 ]) 

259 ], 

260 sort=sort_date, 

261 size=size 

262 ) 

263 return constants.TODO_MANED_STALLED, stalled, sort_date, 0 

264 

265 @classmethod 

266 def maned_follow_up_old(cls, size, maned_of): 

267 sort_date = "created_date" 

268 follow_up_old = TodoQuery( 

269 musts=[ 

270 TodoQuery.cd_older_than(10), 

271 TodoQuery.editor_group(maned_of), 

272 TodoQuery.is_new_application() 

273 ], 

274 must_nots=[ 

275 TodoQuery.status([ 

276 constants.APPLICATION_STATUS_ACCEPTED, 

277 constants.APPLICATION_STATUS_REJECTED, 

278 constants.APPLICATION_STATUS_ON_HOLD 

279 ]) 

280 ], 

281 sort=sort_date, 

282 size=size 

283 ) 

284 return constants.TODO_MANED_FOLLOW_UP_OLD, follow_up_old, sort_date, 0 

285 

286 @classmethod 

287 def maned_ready(cls, size, maned_of): 

288 sort_date = "created_date" 

289 ready = TodoQuery( 

290 musts=[ 

291 TodoQuery.status([constants.APPLICATION_STATUS_READY]), 

292 TodoQuery.editor_group(maned_of), 

293 TodoQuery.is_new_application() 

294 ], 

295 sort=sort_date, 

296 size=size 

297 ) 

298 return constants.TODO_MANED_READY, ready, sort_date, 1 

299 

300 @classmethod 

301 def maned_completed(cls, size, maned_of): 

302 sort_date = "created_date" 

303 completed = TodoQuery( 

304 musts=[ 

305 TodoQuery.status([constants.APPLICATION_STATUS_COMPLETED]), 

306 TodoQuery.lmu_older_than(2), 

307 TodoQuery.editor_group(maned_of), 

308 TodoQuery.is_new_application() 

309 ], 

310 sort=sort_date, 

311 size=size 

312 ) 

313 return constants.TODO_MANED_COMPLETED, completed, sort_date, 0 

314 

315 @classmethod 

316 def maned_assign_pending(cls, size, maned_of): 

317 sort_date = "created_date" 

318 assign_pending = TodoQuery( 

319 musts=[ 

320 TodoQuery.exists("admin.editor_group"), 

321 TodoQuery.lmu_older_than(2), 

322 TodoQuery.status([constants.APPLICATION_STATUS_PENDING]), 

323 TodoQuery.editor_group(maned_of), 

324 TodoQuery.is_new_application() 

325 ], 

326 must_nots=[ 

327 TodoQuery.exists("admin.editor") 

328 ], 

329 sort=sort_date, 

330 size=size 

331 ) 

332 return constants.TODO_MANED_ASSIGN_PENDING, assign_pending, sort_date, 0 

333 

334 @classmethod 

335 def maned_last_month_update_requests(cls, size, maned_of): 

336 som = dates.now().replace(day=1, hour=0, minute=0, second=0, microsecond=0) 

337 now = dates.now() 

338 since_som = int((now - som).total_seconds()) 

339 

340 sort_date = "created_date" 

341 assign_pending = TodoQuery( 

342 musts=[ 

343 TodoQuery.exists("admin.editor_group"), 

344 TodoQuery.cd_older_than(since_som, unit="s"), 

345 # TodoQuery.status([constants.APPLICATION_STATUS_UPDATE_REQUEST]), 

346 TodoQuery.editor_group(maned_of), 

347 TodoQuery.is_update_request() 

348 ], 

349 must_nots=[ 

350 TodoQuery.status([ 

351 constants.APPLICATION_STATUS_ACCEPTED, 

352 constants.APPLICATION_STATUS_REJECTED 

353 ]) 

354 ], 

355 sort=sort_date, 

356 size=size 

357 ) 

358 return constants.TODO_MANED_LAST_MONTH_UPDATE_REQUEST, assign_pending, sort_date, 2 

359 

360 @classmethod 

361 def maned_new_update_requests(cls, size, maned_of): 

362 sort_date = "created_date" 

363 assign_pending = TodoQuery( 

364 musts=[ 

365 TodoQuery.exists("admin.editor_group"), 

366 # TodoQuery.cd_older_than(4), 

367 # TodoQuery.status([constants.APPLICATION_STATUS_UPDATE_REQUEST]), 

368 TodoQuery.editor_group(maned_of), 

369 TodoQuery.is_update_request() 

370 ], 

371 must_nots=[ 

372 TodoQuery.status([ 

373 constants.APPLICATION_STATUS_ACCEPTED, 

374 constants.APPLICATION_STATUS_REJECTED 

375 ]) 

376 ], 

377 sort=sort_date, 

378 size=size 

379 ) 

380 return constants.TODO_MANED_NEW_UPDATE_REQUEST, assign_pending, sort_date, -2 

381 

382 @classmethod 

383 def maned_on_hold(cls, size, account, maned_of): 

384 sort_date = "created_date" 

385 on_holds = TodoQuery( 

386 musts=[ 

387 TodoQuery.is_new_application(), 

388 TodoQuery.status([constants.APPLICATION_STATUS_ON_HOLD]) 

389 ], 

390 ors=[ 

391 TodoQuery.editor_group(maned_of), 

392 TodoQuery.editor(account) 

393 ], 

394 sort=sort_date, 

395 size=size 

396 ) 

397 return constants.TODO_MANED_ON_HOLD, on_holds, sort_date, 0 

398 

399 @classmethod 

400 def editor_stalled(cls, groups, size): 

401 sort_date = "created_date" 

402 stalled = TodoQuery( 

403 musts=[ 

404 TodoQuery.lmu_older_than(6), 

405 TodoQuery.editor_groups(groups), 

406 TodoQuery.is_new_application() 

407 ], 

408 must_nots=[ 

409 TodoQuery.status([ 

410 constants.APPLICATION_STATUS_ACCEPTED, 

411 constants.APPLICATION_STATUS_REJECTED, 

412 constants.APPLICATION_STATUS_READY, 

413 constants.APPLICATION_STATUS_ON_HOLD, 

414 constants.APPLICATION_STATUS_REVISIONS_REQUIRED 

415 ]) 

416 ], 

417 sort=sort_date, 

418 size=size 

419 ) 

420 return constants.TODO_EDITOR_STALLED, stalled, sort_date, 0 

421 

422 @classmethod 

423 def editor_follow_up_old(cls, groups, size): 

424 sort_date = "created_date" 

425 follow_up_old = TodoQuery( 

426 musts=[ 

427 TodoQuery.cd_older_than(8), 

428 TodoQuery.editor_groups(groups), 

429 TodoQuery.is_new_application() 

430 ], 

431 must_nots=[ 

432 TodoQuery.status([ 

433 constants.APPLICATION_STATUS_ACCEPTED, 

434 constants.APPLICATION_STATUS_REJECTED, 

435 constants.APPLICATION_STATUS_READY, 

436 constants.APPLICATION_STATUS_REVISIONS_REQUIRED, 

437 constants.APPLICATION_STATUS_ON_HOLD 

438 ]) 

439 ], 

440 sort=sort_date, 

441 size=size 

442 ) 

443 return constants.TODO_EDITOR_FOLLOW_UP_OLD, follow_up_old, sort_date, 0 

444 

445 @classmethod 

446 def editor_completed(cls, groups, size): 

447 sort_date = "created_date" 

448 completed = TodoQuery( 

449 musts=[ 

450 TodoQuery.status([constants.APPLICATION_STATUS_COMPLETED]), 

451 TodoQuery.editor_groups(groups), 

452 TodoQuery.is_new_application() 

453 ], 

454 sort=sort_date, 

455 size=size 

456 ) 

457 return constants.TODO_EDITOR_COMPLETED, completed, sort_date, 1 

458 

459 @classmethod 

460 def editor_assign_pending(cls, groups, size): 

461 sort_date = "created_date" 

462 assign_pending = TodoQuery( 

463 musts=[ 

464 TodoQuery.editor_groups(groups), 

465 TodoQuery.status([constants.APPLICATION_STATUS_PENDING]), 

466 TodoQuery.is_new_application() 

467 ], 

468 must_nots=[ 

469 TodoQuery.exists("admin.editor") 

470 ], 

471 sort=sort_date, 

472 size=size 

473 ) 

474 return constants.TODO_EDITOR_ASSIGN_PENDING, assign_pending, sort_date, 1 

475 

476 @classmethod 

477 def associate_stalled(cls, acc_id, size): 

478 sort_field = "created_date" 

479 stalled = TodoQuery( 

480 musts=[ 

481 TodoQuery.lmu_older_than(3), 

482 TodoQuery.editor(acc_id), 

483 TodoQuery.is_new_application() 

484 ], 

485 must_nots=[ 

486 TodoQuery.status([ 

487 constants.APPLICATION_STATUS_ACCEPTED, 

488 constants.APPLICATION_STATUS_REJECTED, 

489 constants.APPLICATION_STATUS_READY, 

490 constants.APPLICATION_STATUS_COMPLETED, 

491 constants.APPLICATION_STATUS_REVISIONS_REQUIRED, 

492 constants.APPLICATION_STATUS_ON_HOLD 

493 ]) 

494 ], 

495 sort=sort_field, 

496 size=size 

497 ) 

498 return constants.TODO_ASSOCIATE_PROGRESS_STALLED, stalled, sort_field, 0 

499 

500 @classmethod 

501 def associate_follow_up_old(cls, acc_id, size): 

502 sort_field = "created_date" 

503 follow_up_old = TodoQuery( 

504 musts=[ 

505 TodoQuery.cd_older_than(6), 

506 TodoQuery.editor(acc_id), 

507 TodoQuery.is_new_application() 

508 ], 

509 must_nots=[ 

510 TodoQuery.status([ 

511 constants.APPLICATION_STATUS_ACCEPTED, 

512 constants.APPLICATION_STATUS_REJECTED, 

513 constants.APPLICATION_STATUS_READY, 

514 constants.APPLICATION_STATUS_COMPLETED, 

515 constants.APPLICATION_STATUS_REVISIONS_REQUIRED, 

516 constants.APPLICATION_STATUS_ON_HOLD 

517 ]) 

518 ], 

519 sort=sort_field, 

520 size=size 

521 ) 

522 return constants.TODO_ASSOCIATE_FOLLOW_UP_OLD, follow_up_old, sort_field, 0 

523 

524 @classmethod 

525 def associate_start_pending(cls, acc_id, size): 

526 sort_field = "created_date" 

527 assign_pending = TodoQuery( 

528 musts=[ 

529 TodoQuery.editor(acc_id), 

530 TodoQuery.status([constants.APPLICATION_STATUS_PENDING]), 

531 TodoQuery.is_new_application() 

532 ], 

533 sort=sort_field, 

534 size=size 

535 ) 

536 return constants.TODO_ASSOCIATE_START_PENDING, assign_pending, sort_field, 0 

537 

538 @classmethod 

539 def associate_all_applications(cls, acc_id, size): 

540 sort_field = "created_date" 

541 all = TodoQuery( 

542 musts=[ 

543 TodoQuery.editor(acc_id), 

544 TodoQuery.is_new_application() 

545 ], 

546 must_nots=[ 

547 TodoQuery.status([ 

548 constants.APPLICATION_STATUS_ACCEPTED, 

549 constants.APPLICATION_STATUS_REJECTED, 

550 constants.APPLICATION_STATUS_READY, 

551 constants.APPLICATION_STATUS_COMPLETED, 

552 constants.APPLICATION_STATUS_REVISIONS_REQUIRED, 

553 constants.APPLICATION_STATUS_ON_HOLD 

554 ]) 

555 ], 

556 sort=sort_field, 

557 size=size 

558 ) 

559 return constants.TODO_ASSOCIATE_ALL_APPLICATIONS, all, sort_field, -1 

560 

561 # @classmethod 

562 # def urgent_flags_all(cls, acc_id, size): 

563 # sort_field = "most_urgent_flag_deadline" 

564 # 

565 # all = TodoQuery( 

566 # musts=[ 

567 # TodoQuery.flagged_to_me(acc_id), 

568 # TodoQuery.urgent_flags() 

569 # ], 

570 # sort=sort_field, 

571 # size=size 

572 # ) 

573 # return constants.TODO_URGENT_FLAGS_ALL, all, sort_field, 4 

574 # 

575 # @classmethod 

576 # def regular_flags_all(cls, acc_id, size): 

577 # sort_field = "most_urgent_flag_deadline" 

578 # all = TodoQuery( 

579 # musts=[ 

580 # TodoQuery.flagged_to_me(acc_id) 

581 # ], 

582 # must_nots=[ 

583 # TodoQuery.urgent_flags() 

584 # ], 

585 # sort=sort_field, 

586 # size=size 

587 # ) 

588 # return constants.TODO_REGULAR_FLAGS_ALL, all, sort_field, 3 

589 # 

590 # @classmethod 

591 # def urgent_flags_new_applications(cls, acc_id, size): 

592 # sort_field = "most_urgent_flag_deadline" 

593 # all = TodoQuery( 

594 # musts=[ 

595 # TodoQuery.flagged_to_me(acc_id), 

596 # TodoQuery.urgent_flags(), 

597 # TodoQuery.is_new_application() 

598 # ], 

599 # sort=sort_field, 

600 # size=size 

601 # ) 

602 # return constants.TODO_URGENT_FLAGS_NEW_APPLICATIONS, all, sort_field, 4 

603 # 

604 # @classmethod 

605 # def urgent_flags_update_requests(cls, acc_id, size): 

606 # sort_field = "most_urgent_flag_deadline" 

607 # all = TodoQuery( 

608 # musts=[ 

609 # TodoQuery.flagged_to_me(acc_id), 

610 # TodoQuery.urgent_flags(), 

611 # TodoQuery.is_update_request() 

612 # ], 

613 # sort=sort_field, 

614 # size=size 

615 # ) 

616 # return constants.TODO_URGENT_FLAGS_UPDATE_REQUESTS, all, sort_field, 4 

617 # 

618 # @classmethod 

619 # def urgent_flags_onhold(cls, acc_id, size): 

620 # sort_field = "most_urgent_flag_deadline" 

621 # all = TodoQuery( 

622 # musts=[ 

623 # TodoQuery.flagged_to_me(acc_id), 

624 # TodoQuery.urgent_flags(), 

625 # TodoQuery.is_new_application(), 

626 # TodoQuery.status([constants.APPLICATION_STATUS_ON_HOLD]) 

627 # ], 

628 # sort=sort_field, 

629 # size=size 

630 # ) 

631 # return constants.TODO_URGENT_FLAGS_ONHOLD, all, sort_field, 4 

632 # 

633 # @classmethod 

634 # def regular_flags_new_applications(cls, acc_id, size): 

635 # sort_field = "most_urgent_flag_deadline" 

636 # all = TodoQuery( 

637 # musts=[ 

638 # TodoQuery.flagged_to_me(acc_id), 

639 # TodoQuery.is_new_application() 

640 # ], 

641 # must_nots=[ 

642 # TodoQuery.urgent_flags() 

643 # ], 

644 # sort=sort_field, 

645 # size=size 

646 # ) 

647 # return constants.TODO_REGULAR_FLAGS_NEW_APPLICATIONS, all, sort_field, 3 

648 # 

649 # @classmethod 

650 # def regular_flags_update_requests(cls, acc_id, size): 

651 # sort_field = "most_urgent_flag_deadline" 

652 # all = TodoQuery( 

653 # musts=[ 

654 # TodoQuery.flagged_to_me(acc_id), 

655 # TodoQuery.is_update_request() 

656 # ], 

657 # must_nots=[ 

658 # TodoQuery.urgent_flags() 

659 # ], 

660 # sort=sort_field, 

661 # size=size 

662 # ) 

663 # return constants.TODO_REGULAR_FLAGS_UPDATE_REQUESTS, all, sort_field, 3 

664 # 

665 # @classmethod 

666 # def regular_flags_onhold(cls, acc_id, size): 

667 # sort_field = "most_urgent_flag_deadline" 

668 # 

669 # all = TodoQuery( 

670 # musts=[ 

671 # TodoQuery.flagged_to_me(acc_id), 

672 # TodoQuery.is_new_application(), 

673 # TodoQuery.status([constants.APPLICATION_STATUS_ON_HOLD]) 

674 # ], 

675 # must_nots=[ 

676 # TodoQuery.urgent_flags() 

677 # ], 

678 # sort=sort_field, 

679 # size=size 

680 # ) 

681 # return constants.TODO_REGULAR_FLAGS_ONHOLD, all, sort_field, 3 

682 

683 

684class TodoQuery(object): 

685 """ 

686 ~~->$Todo:Query~~ 

687 ~~^->Elasticsearch:Technology~~ 

688 """ 

689 lmu_sort = {"last_manual_update": {"order": "asc"}} 

690 # cd_sort = {"created_date" : {"order" : "asc"}} 

691 # NOTE that admin.date_applied and created_date should be the same for applications, but for some reason this is not always the case 

692 # therefore, we take a created_date sort to mean a date_applied sort 

693 cd_sort = {"admin.date_applied": {"order": "asc"}} 

694 flag_sort = {"index.most_urgent_flag_deadline": {"order": "asc"}} 

695 

696 sort_map = { 

697 "last_manual_update": lmu_sort, 

698 "created_date": cd_sort, 

699 "most_urgent_flag_deadline": flag_sort 

700 } 

701 

702 def __init__(self, musts=None, must_nots=None, ors=None, sort="last_manual_update", size=10): 

703 self._musts = [] if musts is None else musts 

704 self._must_nots = [] if must_nots is None else must_nots 

705 self._ors = [] if ors is None else ors 

706 self._sort = self.sort_map.get(sort, self.cd_sort) 

707 self._size = size 

708 

709 def query(self): 

710 q = { 

711 "query": { 

712 "bool": { 

713 "must": self._musts, 

714 "must_not": self._must_nots 

715 } 

716 }, 

717 "sort": [ 

718 self._sort 

719 ], 

720 "size": self._size 

721 } 

722 

723 if len(self._musts) > 0: 

724 q["query"]["bool"]["must"] = self._musts 

725 if len(self._must_nots) > 0: 

726 q["query"]["bool"]["must_not"] = self._must_nots 

727 if len(self._ors) > 0: 

728 q["query"]["bool"]["should"] = self._ors 

729 q["query"]["bool"]["minimum_should_match"] = 1 

730 

731 return q 

732 

733 @classmethod 

734 def is_new_application(cls): 

735 return { 

736 "term": { 

737 "admin.application_type.exact": constants.APPLICATION_TYPE_NEW_APPLICATION 

738 } 

739 } 

740 

741 @classmethod 

742 def is_update_request(cls): 

743 return { 

744 "term": { 

745 "admin.application_type.exact": constants.APPLICATION_TYPE_UPDATE_REQUEST 

746 } 

747 } 

748 

749 @classmethod 

750 def editor_group(cls, groups): 

751 return { 

752 "terms": { 

753 "admin.editor_group.exact": [g.name for g in groups] 

754 } 

755 } 

756 

757 @classmethod 

758 def lmu_older_than(cls, weeks): 

759 return { 

760 "range": { 

761 "last_manual_update": { 

762 "lte": "now-" + str(weeks) + "w" 

763 } 

764 } 

765 } 

766 

767 @classmethod 

768 def cd_older_than(cls, count, unit="w"): 

769 return { 

770 "range": { 

771 "admin.date_applied": { 

772 "lte": "now-" + str(count) + unit 

773 } 

774 } 

775 } 

776 

777 @classmethod 

778 def status(cls, statuses): 

779 return { 

780 "terms": { 

781 "admin.application_status.exact": statuses 

782 } 

783 } 

784 

785 @classmethod 

786 def exists(cls, field): 

787 return { 

788 "exists": { 

789 "field": field 

790 } 

791 } 

792 

793 @classmethod 

794 def editor_groups(cls, groups): 

795 gids = [g.name for g in groups] 

796 return { 

797 "terms": { 

798 "admin.editor_group.exact": gids 

799 } 

800 } 

801 

802 @classmethod 

803 def editor(cls, acc_id): 

804 return { 

805 "terms": { 

806 "admin.editor.exact": [acc_id], 

807 } 

808 } 

809 

810 # @classmethod 

811 # def flagged_to_me(cls, acc_id): 

812 # return { 

813 # "terms": { 

814 # "index.flag_assignees.exact": [acc_id] 

815 # } 

816 # } 

817 # 

818 # @classmethod 

819 # def urgent_flags(cls): 

820 # return { 

821 # "range": { 

822 # "index.most_urgent_flag_deadline": { 

823 # "lte": "now+7d/d" 

824 # } 

825 # } 

826 # } 

827 # 

828 # @classmethod 

829 # def flags_with_nonurgent_deadline(cls): 

830 # return { 

831 # "range": { 

832 # "index.most_urgent_flag_deadline": { 

833 # "gt": "now+7d/d" 

834 # } 

835 # } 

836 # } 

837 

838 

839class GroupStatsQuery(): 

840 """ 

841 ~~->$GroupStats:Query~~ 

842 ~~^->Elasticsearch:Technology~~ 

843 """ 

844 

845 def __init__(self, group_name, editor_count=10): 

846 self.group_name = group_name 

847 self.editor_count = editor_count 

848 

849 def query(self): 

850 return { 

851 "track_total_hits": True, 

852 "query": { 

853 "bool": { 

854 "must": [ 

855 { 

856 "term": { 

857 "admin.editor_group.exact": self.group_name 

858 } 

859 } 

860 ], 

861 "must_not": [ 

862 { 

863 "terms": { 

864 "admin.application_status.exact": [ 

865 constants.APPLICATION_STATUS_ACCEPTED, 

866 constants.APPLICATION_STATUS_REJECTED 

867 ] 

868 } 

869 } 

870 ] 

871 } 

872 }, 

873 "size": 0, 

874 "aggs": { 

875 "editor": { 

876 "terms": { 

877 "field": "admin.editor.exact", 

878 "size": self.editor_count 

879 }, 

880 "aggs": { 

881 "application_type": { 

882 "terms": { 

883 "field": "admin.application_type.exact", 

884 "size": 2 

885 } 

886 } 

887 } 

888 }, 

889 "status": { 

890 "terms": { 

891 "field": "admin.application_status.exact", 

892 "size": len(constants.APPLICATION_STATUSES_ALL) 

893 }, 

894 "aggs": { 

895 "application_type": { 

896 "terms": { 

897 "field": "admin.application_type.exact", 

898 "size": 2 

899 } 

900 } 

901 } 

902 }, 

903 "unassigned": { 

904 "missing": { 

905 "field": "admin.editor.exact" 

906 }, 

907 "aggs": { 

908 "application_type": { 

909 "terms": { 

910 "field": "admin.application_type.exact", 

911 "size": 2 

912 } 

913 } 

914 } 

915 } 

916 } 

917 } 

918 

919 

920class HistoricalNumbersQuery: 

921 """ 

922 ~~->$HistoricalNumbers:Query~~ 

923 ~~^->Elasticsearch:Technology~~ 

924 """ 

925 

926 def __init__(self, editor, application_status, editor_group=None, year=None): 

927 self.editor_group = editor_group 

928 self.editor = editor 

929 self.application_status = application_status 

930 self.year = year 

931 

932 def query(self): 

933 if self.year is None: 

934 date_range = {"gte": "now/y", "lte": "now"} 

935 else: 

936 date_range = { 

937 "gte": f"{self.year}-01-01", 

938 "lte": f"{self.year}-12-31" 

939 } 

940 must_terms = [{"range": {"last_updated": date_range}}, 

941 {"term": {"type": "suggestion"}}, 

942 {"term": {"user.exact": self.editor}}, 

943 {"term": {"action": self.application_status}} 

944 ] 

945 

946 if self.editor_group: 

947 must_terms.append({"term": {"editor_group": self.editor_group}}) 

948 

949 return { 

950 "query": { 

951 "bool": { 

952 "must": must_terms 

953 } 

954 } 

955 } 

956