# `MishkaCmsCoreWeb.Live.Admin.Components.KitUpgradePanel`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core_web/live/admin/components/kit_upgrade_panel.ex#L5)

Where a site stands against the version of an extension it is running, read and drawn.

Setting a site up installs what it is missing and never touches what it has, which is the only
safe thing a button called "Set up" can do — and it leaves a site that was provisioned months ago
on months-old components and months-old pages for ever. This is the other half, and it has two
halves of its own because a site has two things to fall behind on:

  * `MishkaCmsCore.Runtime.Handlers.UiKitUpgrade` says where each of the kit's COMPONENTS stands,
    and writes the ones the admin chooses;
  * `MishkaCmsCore.Runtime.PageSamples` says where each of the extension's sample PAGES stands,
    and offers the newer ones as drafts. A page is somebody's work in a way a kit component is
    not — it is what their readers are looking at — so nothing here writes a live page at all.
    A page whose newer version draws a component this site has not got is not offered either:
    the two halves fall behind together, and publishing that draft would put an error message
    where the block should be.

Both report BEFORE anything is written, and the admin decides.

## Wiring

One call in `mount/3` and three event clauses. Nothing draws this directly: `standings/1` is
drawn inside the modal
`MishkaCmsCoreWeb.Live.Admin.Components.ExtensionInstallerPanel.doctor_modal/1` opens, which is
where the site picker and the `kit_check` form live.

    socket =
      KitUpgradePanel.setup(socket,
        bundle: MishkaBlog.Kit.bundle_path(),
        noun: "blog components",
        pages: &MishkaBlog.Provision.sample_pages/2,
        draft_label: "Blog sample page"
      )

    def handle_event("kit_check", params, socket),
      do: {:noreply, KitUpgradePanel.check(socket, chosen_site(socket, params), socket.assigns.may_provision?)}

    def handle_event("kit_upgrade", %{"mode" => mode}, socket),
      do: {:noreply, KitUpgradePanel.upgrade(socket, mode, socket.assigns.may_provision?)}

    def handle_event("page_drafts", _params, socket),
      do: {:noreply, KitUpgradePanel.offer(socket, socket.assigns.may_provision?)}

Which site the check runs against is the dashboard's answer, not this module's: a tenant admin has
one site and a master picks from the ones their page offered, and that is the same question the
"Set up" button already answers. `check/3` takes what `chosen_site/2` decided — a site id, `:none`
or `:refused` — and the permission the dashboard already computed, so the wording of a refusal is
written once here rather than once per extension.

## Why the two component buttons are two

The write is `upgrade!/3` with `:safe` or `:all`, and nothing here presses `:all` on the
admin's behalf. A row whose fingerprint no longer matches what the kit installed is a row
somebody worked on, and the only honest thing to do with it is to say so and wait — which is why
the second button spells out that the version being replaced stays in the component's history.

The pages have one button and it is the safe one, because there is no unsafe one to offer: a draft
is the whole of what this can do to a page.

# `check`

```elixir
@spec check(Phoenix.LiveView.Socket.t(), String.t() | :none | :refused, boolean()) ::
  Phoenix.LiveView.Socket.t()
```

Reads where the site stands and puts it on the socket. Writes nothing.

`site_id` is whatever the dashboard's own site check returned, and `allowed?` the permission it
already computed — both refusals are answered here so every extension says the same thing.

# `offer`

```elixir
@spec offer(Phoenix.LiveView.Socket.t(), boolean()) :: Phoenix.LiveView.Socket.t()
```

Offers every sample page the site is behind on as a draft of itself, and redraws the standings
from the database afterwards.

Takes no mode, because there is only one thing to do: a draft is not a change to anything anybody
is reading, so there is no second, riskier button for the admin to be asked about.

# `setup`

```elixir
@spec setup(Phoenix.LiveView.Socket.t(), keyword()) :: Phoenix.LiveView.Socket.t()
```

One-call wiring for a dashboard: assigns `@kit_upgrade`, which `standings/1` is given whole.

## Options

  * `:bundle` — the extension's bundle path, e.g. `MishkaBlog.Kit.bundle_path()`.
  * `:noun` — what this kit's components are called in a sentence ("blog components"), used
    when a read or a write fails and the admin is told which of them it was about.
  * `:pages` — a two-argument function from a site id and the actor to the extension's sample
    pages as attribute maps, e.g. `&MishkaBlog.Provision.sample_pages/2`. The same function the "Set up"
    button's own pages come from, so this cannot be comparing against different ones.
  * `:draft_label` — what a draft this creates is called in the builder's picker.

# `standings`

Where the site stands, given the `@kit_upgrade` assign `setup/2` wrote: the components in one
block and the pages in the other, neither drawn until `check/3` has read.

The site picker and the `kit_check` form that fill these in belong to the modal these are drawn
in — `MishkaCmsCoreWeb.Live.Admin.Components.ExtensionInstallerPanel.doctor_modal/1`.

## Attributes

* `setup` (`:map`) (required)
* `id` (`:string`) (required)

# `upgrade`

```elixir
@spec upgrade(Phoenix.LiveView.Socket.t(), String.t(), boolean()) ::
  Phoenix.LiveView.Socket.t()
```

Writes the components the admin asked for and redraws the standings from the database afterwards.

`mode` comes from the button that was pressed, so anything but `"all"` is taken as the safe
half — an unexpected value cannot be read as permission to write over an edited row.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
