All files / src/analysis record.ts

100% Statements 33/33
100% Branches 5/5
100% Functions 2/2
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98                                                            2x 2x     2x                     2x                 2x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x                       2x 177x 177x 177x 177x       177x 177x 177x 177x 177x 177x 177x 177x 177x 177x  
/**
 * A run, written the way the console records it (§23).
 *
 * Two lines of policy, and both are here rather than in `runner.ts` because
 * both are worth a test and neither is about queueing:
 *
 * **The command is the drawer's command.** `cliCommandFor` is called with the
 * same `Provenance` the `copy CLI command` button passes it, so a line in the
 * console and a line on the clipboard cannot be two accounts of one run. That
 * matters more here than it looks: the console records a run when it *happens*,
 * and the drawer offers one for the result on screen, so the two are usually
 * looking at the same job from an hour apart.
 *
 * **Every run is a command.** For a while the CLI refused the tasks that read
 * a bigWig or a second map, and a run of one was recorded as a sentence with
 * the refusal under it — a record with holes in it exactly where a reader is
 * least likely to notice. `cli/main.ts` takes every task now, so `cliCommandFor`
 * is total and the only thing left to keep apart is its caveats: the `#` lines
 * above a command are what a shell cannot be told, and the console shows them
 * beside the line rather than folded into it.
 *
 * The provisional provenance below is the other half of recording a run when it
 * starts. A result carries its own, written in the worker at the moment the
 * numbers come back — but a *failed* run has no result, and a failure is
 * precisely when a reader wants to know what was asked for. So one is composed
 * from the job, field for field as the worker composes it, and is replaced by
 * the worker's the moment there is one ({@link runDraft} is called twice, and
 * `AnalysisRunner` hands the second to `endAction`).
 */
 
import { cliCommandFor } from './cliCommand'
import { provenanceLines } from './write/tsv'
import type { AnalysisContext, AnalysisFraming, AnalysisTask, Params, Provenance } from './types'
import type { ActionDraft } from '../state/actions'
import { COMMIT, VERSION } from '../version'
 
/**
 * The build stamp a result would carry, spelled as the worker spells it.
 *
 * `analysis.worker.ts` composes exactly this and calls it `APP`. Two copies of
 * one string is a smell, and the alternative is worse: importing the worker
 * module on the main thread to read a constant off it drags h5wasm into the
 * shell's bundle. The value is two build-time constants joined, so the risk is
 * that they *stop* agreeing rather than that they are computed differently.
 */
const APP = [VERSION, COMMIT].filter((part) => part !== '').join(' ')
 
/**
 * What a run's provenance is before there is a result to read one off.
 *
 * The parameters and the framing as they were queued, which is what
 * `Provenance` means by them — *what was asked for* — so this is the same
 * record the worker would write, minus the moment it finished.
 */
export function provisionalProvenance(
  task: AnalysisTask,
  params: Params,
  framing: AnalysisFraming,
): Provenance {
  return {
    task: task.id,
    taskVersion: task.version,
    params,
    framing,
    app: APP,
    createdAt: new Date().toISOString(),
  }
}
 
export interface RunDraftInput {
  task: AnalysisTask
  provenance: Provenance
  /** The session's own facts — what turns bins into a locus and ids into names. */
  context: AnalysisContext | null
  /** Which map this run measured, where the session names more than one. */
  where?: string
}
 
/** One run, as the console's list holds it. */
export function runDraft(input: RunDraftInput): ActionDraft {
  const { task, provenance, context, where } = input
  const command = cliCommandFor({ task, provenance, context })
  const detail = provenanceLines(provenance)
  const seat = where === undefined ? {} : { where }
  // `cliCommandFor` writes its caveats as `#` lines above the command and joins
  // the lot; the console keeps them apart, because one of them is what a shell
  // would take and the others are what it cannot be told.
  const lines = command.text.split('\n')
  return {
    kind: 'run',
    line: lines[lines.length - 1],
    shell: true,
    notes: lines.slice(0, -1),
    detail,
    ...seat,
  }
}