Class ViewStore

java.lang.Object
io.jenkins.plugins.interactiveinput.view.ViewStore

@Extension public class ViewStore extends Object
In-memory registry of ReviewDocuments published by the interactiveView step, with XStream persistence of the metadata to $JENKINS_HOME/interactive-input/views.xml and the content versions stored as separate files under $JENKINS_HOME/interactive-input/views/<id>/.

Deliberately mirrors QuestionStore: a ConcurrentHashMap of documents, all transitions on a single document serialised via synchronized (doc), a transient resolver map that resumes a paused wait:true step when a decision is recorded, and SLA/retention driven by the shared SlaTicker. Keeping content out of the XML (in per-version files) keeps the metadata file small even for large reports.

  • Constructor Details

    • ViewStore

      public ViewStore()
  • Method Details

    • get

      @NonNull public static ViewStore get()
    • submit

      @NonNull public ReviewDocument submit(@NonNull ReviewDocument doc, @NonNull String content)
      Register a new review document and persist its metadata plus the original content snapshot.
      Parameters:
      doc - the document metadata (its version 1 is the original snapshot)
      content - the snapshotted file content (UTF-8 text)
      Returns:
      the same document for convenience
    • register

      public void register(@NonNull String id, @NonNull ViewStore.Resolution resolution)
      Attach a paused (wait:true) step's resolver. If the document has already been decided or expired (e.g. it settled while the step was resuming after a restart), the resolver runs at once.
    • unregister

      public void unregister(@NonNull String id)
    • get

      @CheckForNull public ReviewDocument get(@NonNull String id)
    • remove

      public void remove(@NonNull String id)
      Remove a document outright (metadata + all content versions).
    • addComment

      @NonNull public ReviewComment addComment(@NonNull String id, int line, @NonNull String body, @NonNull String byUserId)
      Add a root comment. Callers must pre-check permissions (the REST/UI layer's responsibility).
      Throws:
      IllegalStateException - if the document is missing or not commentable
    • addComment

      @NonNull public ReviewComment addComment(@NonNull String id, int line, @NonNull String body, @NonNull String byUserId, @CheckForNull String parentId, @CheckForNull String authorLabel)
      Add a comment, optionally as a threaded reply and/or with a display-only author label. Callers must pre-check permissions (the REST/UI layer's responsibility). The byUserId audit author is always the real, server-set identity; authorLabel only changes what the viewer displays.

      Threads are kept one level deep: a reply whose parentId points at another reply is re-parented to that reply's root, so every stored reply references a root comment and the viewer can render a simple parent→replies tree.

      Parameters:
      parentId - the comment being replied to, or null for a root comment
      authorLabel - display-only label (e.g. "AI response"), or null to show the real author
      Throws:
      IllegalStateException - if the document is missing or not commentable
      IllegalArgumentException - if parentId does not reference an existing comment
    • addComment

      @NonNull public ReviewComment addComment(@NonNull String id, int line, @NonNull String body, @NonNull String byUserId, @CheckForNull String parentId, @CheckForNull String authorLabel, @CheckForNull String quote)
      Add a comment that also records the verbatim quote the reviewer highlighted (see the viewer's highlight-select flow). Anchoring still uses line; quote is display-only context, so a value is kept even when it is a sub-phrase of the line or spans several lines. Callers must pre-check permissions (the REST/UI layer's responsibility).
      Parameters:
      quote - the highlighted snippet to show back verbatim, or null for the "+"/general path
      Throws:
      IllegalStateException - if the document is missing or not commentable
      IllegalArgumentException - if parentId does not reference an existing comment
    • setCommentResolved

      public void setCommentResolved(@NonNull String id, @NonNull String commentId, boolean resolved)
      Toggle a comment's resolved flag.
      Throws:
      IllegalStateException - if the document or comment is missing
    • saveEdit

      public int saveEdit(@NonNull String id, @NonNull String newContent, @NonNull String byUserId)
      Save an edit to the durable review copy as a new content version with the default "edited" note.
      Returns:
      the new version index
      Throws:
      IllegalStateException - if the document is missing, not editable, or in a final decided state (any decision other than CHANGES_REQUESTED; see ReviewStatus.allowsEdit())
    • saveEdit

      public int saveEdit(@NonNull String id, @NonNull String newContent, @NonNull String byUserId, @CheckForNull String note)
      Save an edit to the durable review copy as a new content version, recording an optional display-only note (e.g. an AI course-correction summary) in the version history. Callers must pre-check permissions and that the document is editable.
      Parameters:
      note - a short edit summary shown in the version dropdown, or null for the default label
      Returns:
      the new version index
      Throws:
      IllegalStateException - if the document is missing, not editable, or in a final decided state (any decision other than CHANGES_REQUESTED; see ReviewStatus.allowsEdit())
    • decide

      public void decide(@NonNull String id, @NonNull ReviewStatus decision, @NonNull String byUserId, @NonNull String source)
      Record a reviewer decision and, for a blocking view, resume the paused pipeline. Callers must pre-check permissions.
      Throws:
      IllegalStateException - if the document is missing or already decided
    • abandonForShutdown

      public void abandonForShutdown(@NonNull String id)
      Mark an open blocking review as ABORTED without firing its resolver. Used when the owning step is stopped (build aborted): the framework already delivers the abort to the pipeline, so we only need to move the document to a terminal, compactable state.
    • readContent

      @CheckForNull public String readContent(@NonNull String id, int version)
      Returns:
      the content of a specific version as UTF-8 text, or null if unavailable.
    • readCurrentContent

      @CheckForNull public String readCurrentContent(@NonNull String id)
      Returns:
      the current version's content as UTF-8 text, or null if unavailable.
    • listOpenReadable

      @NonNull public List<ReviewDocument> listOpenReadable()
      Returns:
      every OPEN review the current user can read (admin ?all=true list).
    • listNotifications

      @NonNull public List<ReviewDocument> listNotifications()
      Returns:
      OPEN, notify-enabled reviews to surface for the current user across all jobs.
    • listNotificationsForJob

      @NonNull public List<ReviewDocument> listNotificationsForJob(@NonNull String jobFullName)
      Returns:
      OPEN, notify-enabled reviews to surface for the current user, scoped to one job.
    • countNotificationsForJob

      public int countNotificationsForJob(@NonNull String jobFullName)
    • countNotificationsForBuild

      public int countNotificationsForBuild(@NonNull String jobFullName, int buildNumber)
      Returns:
      the number of OPEN, notify-enabled reviews to surface for the current user on a single build, honouring the user-scope switch. Drives the run-page sidebar count badge (the per-build equivalent of countNotificationsForJob(String)).
    • hasNotificationForBuild

      public boolean hasNotificationForBuild(@NonNull String jobFullName, int buildNumber)
      Returns:
      true if the build has an OPEN, notify-enabled review the current user should be notified of (drives the build-history badge, honouring the user-scope switch).
    • listForJob

      @NonNull public List<ReviewDocument> listForJob(@NonNull String jobFullName)
      Returns:
      every review (any status) for jobFullName the current user can read.
    • listForBuild

      @NonNull public List<ReviewDocument> listForBuild(@NonNull String jobFullName, int buildNumber)
      Returns:
      every review (any status) for a specific build the current user can read.
    • hasAnyForBuild

      public boolean hasAnyForBuild(@NonNull String jobFullName, int buildNumber)
      Existence probe (no permission filter) used by the run-action factory. Anyone reaching a build page already holds Item.READ, so this leaks nothing the permission-checked page would not.
      Returns:
      true if any review (any status) is recorded for this build.
    • canView

      public boolean canView(@NonNull ReviewDocument doc)
      Returns:
      true if the current authentication can read the source job (Item.READ).
    • canContribute

      public boolean canContribute(@NonNull ReviewDocument doc)
      Returns:
      true if the current authentication may contribute (comment / edit / decide): Item.BUILD on the job, or membership in the document's submitterFilter (with administrators always allowed). Mirrors QuestionStore.canAnswer(io.jenkins.plugins.interactiveinput.model.Question).
    • canContributeEffective

      public boolean canContributeEffective(@NonNull ReviewDocument doc)
      Returns:
      true if the current authentication may contribute (comment / edit / decide) after applying the lock-to-build-starter switch. This is canContribute(ReviewDocument) unless the switch is on, in which case only the build starter (or a Jenkins administrator) may contribute; reviews with no human starter are never locked. This is the check the REST mutation endpoints and the page's canContribute flag use — it can only restrict, never widen, access. Mirrors QuestionStore.canAnswerEffective(io.jenkins.plugins.interactiveinput.model.Question), so "only the build starter may answer (others can view)" governs reviews exactly as it governs questions.
    • findJob

      @CheckForNull public Job<?,?> findJob(@NonNull ReviewDocument doc)
    • expireOverdue

      public void expireOverdue(long now)
      Expire every overdue blocking review. Called by the SLA ticker.
    • compact

      public void compact(long now, long retentionMs)
      Remove decided reviews older than retentionMs. Called by the SLA ticker.
    • markBuildDeletedForBuild

      public int markBuildDeletedForBuild(@NonNull String jobFullName, int buildNumber)
      Mark every review of a now-deleted build as build-deleted. The reviews are kept as durable audit records (their comment/decision history outlives the build), but they drop out of the notification centre and the sidebar/badge counts and the per-job page renders them as "build deleted". Invoked by the RunListener when a build is deleted.
      Returns:
      the number of reviews newly marked (0 if none matched or all were already marked).
    • reconcileDeletedBuilds

      public int reconcileDeletedBuilds()
      Startup self-heal: mark reviews whose owning build no longer exists (deleted before this cleanup shipped, or while the controller was down). Only acts when the job still resolves but the build is gone, so a temporarily-unresolvable job (folder still loading, security, etc.) never loses its reviews. Invoked once from BuildLifecycleCleanup's onLoaded.
      Returns:
      the number of reviews newly marked.
    • currentUserId

      @NonNull public static String currentUserId()
      Returns:
      the user id of the current authentication, or "SYSTEM" if unauthenticated.