Skip to main content

lib/scoria_web/components/incident_evidence_component.ex

defmodule ScoriaWeb.IncidentEvidenceComponent do
  use Phoenix.Component
  import ScoriaWeb.UI

  attr(:evidence, :map, required: true)

  def render(assigns) do
    ~H"""
    <.notebook
      id="incident-evidence-notebook"
      title="Trace-first incident notebook"
      eyebrow="Incident evidence"
      selected_tab="incident"
    >
      <:tab key="incident" label="Incident">
        <div class="space-y-4">
          <.evidence_section
            title="Composite health rollup"
            description="Composite health rollup stays compact while the evidence below explains the selected run."
          >
            <.evidence_rows
              rows={[
                {"trace", @evidence.trace_id},
                {"run", @evidence.run_id}
              ]}
            />

            <div class="mt-3 grid gap-3 lg:grid-cols-5">
              <.rollup_section
                label="Budget"
                value={@evidence.health_rollup.budget_signal}
                detail={@evidence.health_rollup.budget_detail}
              />
              <.rollup_section
                label="Breaker"
                value={@evidence.health_rollup.breaker_signal}
                detail={@evidence.health_rollup.breaker_detail}
              />
              <.rollup_section
                label="Review incidents"
                value={@evidence.health_rollup.review_count}
                detail="Open review alerts stay visible without becoming pager noise."
              />
              <.rollup_section
                label="Page incidents"
                value={@evidence.health_rollup.page_count}
                detail="Fast burn and breaker trips remain explicit."
              />
              <.rollup_section
                label="Audit relay"
                value={@evidence.health_rollup.relay_signal}
                detail={@evidence.health_rollup.relay_detail}
              />
            </div>
          </.evidence_section>

          <div class="scoria-evidence-split">
            <div class="space-y-4">
              <.evidence_section
                title="Budget strip"
                description="Reservation actuals, reason codes, and provider/tool refs for the selected run."
                badge={@evidence.budget.status_label}
                tone={badge_tone(@evidence.budget.status, :budget)}
              >
                <.evidence_rows
                  rows={[
                    {"Reservation actuals", @evidence.budget.actuals},
                    {"policy", @evidence.budget.policy_key},
                    {"Reason and integration", @evidence.budget.reason_code},
                    {"provider/tool", "#{@evidence.budget.provider_ref} / #{@evidence.budget.tool_ref}"}
                  ]}
                />
              </.evidence_section>

              <.evidence_section title="Incident notebook">
                <div class="space-y-3">
                  <.incident_section :for={incident <- @evidence.incidents} incident={incident} />
                </div>
              </.evidence_section>
            </div>

            <div class="space-y-4">
              <.evidence_section title="Breaker and relay evidence">
                <.evidence_section
                  title={@evidence.breaker.breaker_key}
                  description={"#{@evidence.breaker.reason_code} via #{@evidence.breaker.integration_kind}"}
                  badge={@evidence.breaker.state_label}
                  tone={badge_tone(@evidence.breaker.state, :breaker)}
                >
                  <.evidence_rows
                    rows={[
                      {"breaker key", @evidence.breaker.breaker_key},
                      {"state", @evidence.breaker.state_label},
                      {"reason code", @evidence.breaker.reason_code},
                      {"integration kind", @evidence.breaker.integration_kind}
                    ]}
                  />
                </.evidence_section>

                <div class="mt-3 space-y-3">
                  <.audit_section :for={audit <- @evidence.audit_rows} audit={audit} />
                </div>
              </.evidence_section>

              <.evidence_section title="Notification delivery outcomes">
                <div class="space-y-3">
                  <.delivery_section :for={delivery <- @evidence.deliveries} delivery={delivery} />
                </div>
              </.evidence_section>
            </div>
          </div>
        </div>
      </:tab>
    </.notebook>
    """
  end

  attr(:label, :string, required: true)
  attr(:value, :any, required: true)
  attr(:detail, :string, required: true)

  defp rollup_section(assigns) do
    ~H"""
    <.evidence_section title={@label} badge={to_string(@value)} tone={tone(@value)}>
      <.evidence_rows rows={[{"value", @value}, {"detail", @detail}]} />
    </.evidence_section>
    """
  end

  attr(:incident, :map, required: true)

  defp incident_section(assigns) do
    ~H"""
    <.evidence_section
      title={@incident.summary}
      description={@incident.reason_code}
      badge={@incident.routing_label}
      tone={badge_tone(@incident.routing_class, :routing)}
    >
      <.evidence_rows
        rows={[
          {"routing", @incident.routing_label},
          {"severity", @incident.severity_label},
          {"scorer version", @incident.scorer_version},
          {"baseline version", @incident.baseline_version},
          {"alert reason code", @incident.reason_code},
          {"incident key", @incident.incident_key}
        ]}
      />

      <.evidence_action_row>
        <a class="scoria-button scoria-button--ghost scoria-button--sm" href={"#trace-#{@incident.trace_id}"}>
          Trace <%= @incident.trace_id %>
        </a>
        <a
          :if={@incident.run_id}
          class="scoria-button scoria-button--ghost scoria-button--sm"
          href={"#run-#{@incident.run_id}"}
        >
          Run <%= @incident.run_id %>
        </a>
        <a
          :if={@incident.approval_id}
          class="scoria-button scoria-button--ghost scoria-button--sm"
          href={"#approval-#{@incident.approval_id}"}
        >
          Approval <%= @incident.approval_id %>
        </a>
      </.evidence_action_row>
    </.evidence_section>
    """
  end

  attr(:audit, :map, required: true)

  defp audit_section(assigns) do
    ~H"""
    <.evidence_section
      title={@audit.event_type}
      description={"approval #{@audit.approval_id} - actor #{@audit.actor_ref}"}
      badge={@audit.sink_status}
      tone={badge_tone(@audit.sink_status, :audit)}
    >
      <.evidence_rows
        rows={[
          {"event type", @audit.event_type},
          {"approval", @audit.approval_id},
          {"actor", @audit.actor_ref},
          {"sink status", @audit.sink_status}
        ]}
      />
    </.evidence_section>
    """
  end

  attr(:delivery, :map, required: true)

  defp delivery_section(assigns) do
    ~H"""
    <.evidence_section
      title={@delivery.sink_kind}
      description={@delivery.routing_key}
      badge={@delivery.delivery_status}
      tone={badge_tone(@delivery.delivery_status, :delivery)}
    >
      <.evidence_rows
        rows={[
          {"routing key", @delivery.routing_key},
          {"delivery status", @delivery.delivery_status},
          {"outcome", @delivery.delivery_outcome},
          {"transport mode", @delivery.transport_mode},
          {"transport sink", @delivery.transport_sink},
          {"attempts", @delivery.attempt_count},
          {"last error", @delivery.last_error}
        ]}
      />
    </.evidence_section>
    """
  end

  # Domain (value, kind) -> semantic tone atom. Rendering lives in ScoriaWeb.UI.badge/1.
  defp badge_tone("page", :routing), do: :fail
  defp badge_tone("review", :routing), do: :info
  defp badge_tone("trip", :budget), do: :fail
  defp badge_tone("warn", :budget), do: :warn
  defp badge_tone("open", :breaker), do: :fail
  defp badge_tone("failed", :delivery), do: :fail
  defp badge_tone("pending", :audit), do: :warn
  defp badge_tone(_value, _kind), do: :pass
end