atris 3.74.29 → 3.76.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/gm-season.js CHANGED
@@ -11,7 +11,7 @@
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
13
  const { atrisRating } = require('./atris-rating');
14
- const { isProofNotSaved } = require('./release-kind');
14
+ const { isProofNotSaved, isCouldNotStart } = require('./release-kind');
15
15
 
16
16
  const HOUR_MS = 60 * 60 * 1000;
17
17
  const DAY_MS = 24 * HOUR_MS;
@@ -22,13 +22,14 @@ const ACTIVE_MISSION_STATUSES = new Set(['running', 'ready']);
22
22
  const KEPT_ACTIONS = new Set(['accepted', 'merged']);
23
23
  const FINISHED = new Set(['landed', 'dropped', 'sent_back', 'abandoned']);
24
24
  const FORM_LETTER = { landed: 'k', dropped: 'd', sent_back: 'x', abandoned: 'a' };
25
- const PLAY_WORD = { took: 'took', landed: 'landed', dropped: 'dropped', sent_back: 'sent back', abandoned: 'abandoned', not_needed: 'not needed', held: 'held' };
25
+ const PLAY_WORD = { took: 'took', landed: 'landed', dropped: 'dropped', sent_back: 'sent back', abandoned: 'abandoned', not_needed: 'not needed', held: 'held', could_not_start: 'unstarted' };
26
26
  // A person has this long after a drop to say the job did not need doing.
27
27
  const NOT_NEEDED_WINDOW_MS = 7 * DAY_MS;
28
28
 
29
29
  // An outcome is one play:
30
30
  // { event_id, task_id, member, title, kind, at, claimed_at, landed_by }
31
- // kind is took | landed | dropped | sent_back | abandoned | not_needed | held.
31
+ // kind is took | landed | dropped | sent_back | abandoned | not_needed | held |
32
+ // could_not_start.
32
33
  // abandoned is a claim that ended archived without landing; a job still in
33
34
  // review is pending and not counted. not_needed is a drop or abandon that
34
35
  // someone other than the member later confirmed the job never needed; it is
@@ -36,8 +37,14 @@ const NOT_NEEDED_WINDOW_MS = 7 * DAY_MS;
36
37
  // because the work is done and its pull request waits for a person, but the
37
38
  // proof that says so could not be saved; the release says so in a field
38
39
  // (lib/release-kind.js), never in its words. It is not finished either, so it
39
- // is neither a landing nor a miss. at and claimed_at are epoch ms; claimed_at
40
- // may be null. landed_by is autoland | you, only on landed.
40
+ // is neither a landing nor a miss. could_not_start is a claim a flight let go
41
+ // before the engine ran, because the build machine could not start the job; the
42
+ // release says so in a field the same way. The member never touched the work, so
43
+ // it is not finished and not a miss either. The field checks itself: a claim the
44
+ // flight marked started (a build_started event after its claim, see
45
+ // markBuildStarted in task-db.js) had its engine asked, so a release that says
46
+ // could_not_start on it is a drop like any other. at and claimed_at are epoch
47
+ // ms; claimed_at may be null. landed_by is autoland | you, only on landed.
41
48
 
42
49
  function byTimeAsc(a, b) {
43
50
  return Number(a.at || 0) - Number(b.at || 0);
@@ -50,7 +57,7 @@ function isFinished(outcome) {
50
57
  // jobs = landed + dropped + sent back + abandoned in the window, the same
51
58
  // count the rating divides by. Jobs still in review are pending.
52
59
  function tally(outcomes) {
53
- const out = { took: 0, landed: 0, dropped: 0, sent_back: 0, abandoned: 0, not_needed: 0, held: 0 };
60
+ const out = { took: 0, landed: 0, dropped: 0, sent_back: 0, abandoned: 0, not_needed: 0, held: 0, could_not_start: 0 };
54
61
  for (const o of outcomes || []) {
55
62
  if (Object.prototype.hasOwnProperty.call(out, o.kind)) out[o.kind] += 1;
56
63
  }
@@ -309,6 +316,18 @@ function parseJson(text) {
309
316
  }
310
317
  }
311
318
 
319
+ // When a landing happened. A merge a person made on GitHub is written down later, when the sweep
320
+ // notices it, and its event says when the merge was (landed_at). The rating dates the landing by
321
+ // that, so a merge swept in weeks late does not count as news. It is never later than the event
322
+ // itself, so a time in the future cannot hold a landing inside the window; a time that cannot be
323
+ // read leaves the landing dated when it was written down.
324
+ function landedAt(row, payload) {
325
+ const written = Number(row.created_at);
326
+ const text = payload && typeof payload.landed_at === 'string' ? payload.landed_at.trim() : '';
327
+ const merged = text ? Date.parse(text) : NaN;
328
+ return Number.isFinite(merged) && Number.isFinite(written) ? Math.min(merged, written) : written;
329
+ }
330
+
312
331
  function landedBy(row, payload) {
313
332
  if (payload && (payload.auto_accepted === true || payload.self_landed === true)) return 'autoland';
314
333
  return /auto/i.test(String(row.actor || '')) ? 'autoland' : 'you';
@@ -330,8 +349,13 @@ function outcomeFromEvent(row) {
330
349
  member = row.actor || payload.claimed_by;
331
350
  } else if (row.event_type === 'claim_released') {
332
351
  // A release that carries the proof-not-saved fields is work waiting on a person,
333
- // not a drop. Only the fields say so: the same words with no fields stay a drop.
334
- kind = isProofNotSaved(payload) ? 'held' : 'dropped';
352
+ // not a drop. One that carries the could-not-start kind was let go before the
353
+ // engine ran, so it is not a drop either, unless its claim was marked started
354
+ // (engine_started): then the kind is false and it is a drop. Only the fields say
355
+ // so: the same words with no fields stay a drop.
356
+ kind = isProofNotSaved(payload) ? 'held'
357
+ : isCouldNotStart(payload) && Number(row.engine_started) !== 1 ? 'could_not_start'
358
+ : 'dropped';
335
359
  member = payload.previous_claimed_by || payload.released_by || row.actor;
336
360
  } else if (row.event_type === 'claim_expired') {
337
361
  kind = 'dropped';
@@ -365,7 +389,7 @@ function outcomeFromEvent(row) {
365
389
  member: String(member),
366
390
  title: row.title || '',
367
391
  kind,
368
- at: row.event_type === 'claim_expired' && Number.isFinite(leaseUntil) ? leaseUntil : Number(row.created_at),
392
+ at: row.event_type === 'claim_expired' && Number.isFinite(leaseUntil) ? leaseUntil : kind === 'landed' ? landedAt(row, payload) : Number(row.created_at),
369
393
  reason: kind !== 'took' && kind !== 'landed' ? (typeof payload.reason === 'string' && payload.reason.trim() || typeof payload.note === 'string' && payload.note.trim() || null) : null,
370
394
  claimed_at: claimedAt,
371
395
  landed_by: kind === 'landed' ? landedBy(row, payload) : null,
@@ -420,7 +444,11 @@ function outcomesFromEvents(rows, since = -Infinity) {
420
444
  const ended = ['claim_released', 'claim_expired', 'completed', 'reopened', 'archived'].includes(row.event_type);
421
445
  if (!play) { if (ended) closed.add(key); continue; }
422
446
  if (play.kind === 'took') { plays.push(play); continue; }
423
- if (row.event_type === 'claim_released') {
447
+ // A release that another member's claim follows within two minutes reads as a hand-over, and is left out. That
448
+ // is a rule about old history, whose releases carry no mark, and it holds for them as it always did. A release
449
+ // on a claim the engine started (engine_started) is a real miss whoever claims next, and one that could not
450
+ // start has its own line: neither is a hand-over, so neither is erased.
451
+ if (row.event_type === 'claim_released' && Number(row.engine_started) !== 1 && play.kind !== 'could_not_start') {
424
452
  let next = null;
425
453
  for (let j = i + 1; j < ordered.length && ordered[j].task_id === row.task_id; j++) {
426
454
  if (ordered[j].event_type === 'claimed') { next = ordered[j]; break; }
@@ -555,6 +583,12 @@ function playEventsSql(extraWhere = '') {
555
583
  SELECT e.event_id, e.task_id, e.workspace_root, e.event_type, e.actor, e.payload, e.created_at, e.version,
556
584
  t.title, t.claimed_by, t.claimed_at,
557
585
  c.event_id AS claim_event_id, c.created_at AS claim_event_at, c.actor AS claim_actor,
586
+ CASE WHEN e.event_type = 'claim_released' AND c.event_id IS NOT NULL AND EXISTS (
587
+ SELECT 1 FROM task_events b
588
+ WHERE b.task_id = e.task_id AND b.version > c.version AND b.version < e.version
589
+ -- the flight marks a claim started right before it asks the assigned engine
590
+ AND b.event_type = 'build_started'
591
+ ) THEN 1 ELSE 0 END AS engine_started,
558
592
  CASE WHEN e.event_type = 'archived' AND c.event_id IS NOT NULL AND NOT EXISTS (
559
593
  SELECT 1 FROM task_events x
560
594
  WHERE x.task_id = e.task_id AND x.version > c.version AND x.version < e.version
@@ -643,6 +677,9 @@ function loadSeason(workspaceRoot, { now = Date.now(), days = SEASON_DAYS, dbPat
643
677
  title: t.title,
644
678
  member: String(t.claimed_by),
645
679
  since: Number(t.proof_at || t.last_event_at || t.updated_at || 0),
680
+ // Done, and its pull request still open for a person to land. The rating never asks
681
+ // GitHub; `atris task sweep --held` does, and credits the builder when it was merged.
682
+ ...(taskDb.heldPullRequestOf(t) ? { waiting_on_pull_request: true } : {}),
646
683
  }));
647
684
  season.claims = live
648
685
  .filter(t => t.status === 'claimed' && t.claimed_by)