MishkaCmsMigration.Report (Mishka CMS Migration v0.0.1)

Copy Markdown View Source

What a dry run or an import did with each item, saved on the run (MishkaCmsMigration.MigrationRun's report) and handed to migration:import:finished.

%{
  counts: %{created: 2, updated: 1, skipped: 1, failed: 0, undecided: 0},
  guests: 0,
  kinds: [%{kind: "post", created: 2, updated: 1, skipped: 1, failed: 0, undecided: 0}],
  entries: [
    %{kind: "post", source_id: "12", title: "Hello", outcome: :skipped, reason: "unchanged"},
    ...
  ]
}

The outcome of an entry is one of outcomes/0. In an import it is what was done. In a dry run it is the plan, from what the source and earlier runs say, which the dry run also records on each item (MishkaCmsMigration.MigrationItem's plan); a dry run asks no plugin and does not run the CMS action, so an item a migration:item:before_import plugin would skip, or one the CMS would refuse (an address another source already took, a value its validations reject), reads as created or updated in the dry run and as skipped or failed in the import.

reason says why an item was skipped or failed, and, for an item created or updated, what was written otherwise than the source has it (MishkaCmsMigration.Workers.Helpers.Writer), nil when nothing was: its target's note (MishkaCmsMigration.Target's note/1, such as "draft_from_pending"), then the pieces of its content no plugin converted, kept as escaped text ("unconverted: " and their names), joined by "; ". For a skipped or failed item:

  • "left_out_by_admin" — an admin left the item out on the Items page; a CMS record an import wrote for it is kept as it is.
  • "parent_left_out" — the item sits under one an admin left out, however deep; a CMS record an import wrote for it is kept as it is.
  • "mapped_missing" — an admin mapped the item to a CMS record that has since been deleted.
  • "unchanged" — the source has not changed the item since it was last written.
  • "edited_in_cms" — its CMS record was changed in the CMS after it was written, and is kept.
  • "target_missing" — its CMS record was deleted in the CMS after it was written, and an admin decided to leave it out ("deleted_in_cms", below), so it stays deleted.
  • "left_out_by_admin" too for an item whose CMS record an admin archived and decided to leave archived ("archived_in_cms", below).
  • "no_target" — no target is registered for the item's kind (MishkaCmsMigration.Target).
  • "waits_for_blog" — the Blog is not installed, and an admin decided the item waits for it.
  • "waits_for_admin" — the admin who ran the import may not allow the item's video player on the site, and an admin decided it waits for one who may.
  • "waits_for_language_urls" — the links would not match the site's language addresses, and an admin decided the item waits until they do.
  • "duplicate_in_source" — the source listed this id of this kind before; the first is used.
  • "parent_undecided: <parent>", "parent_missing: <parent>" or "parent_not_listed: <id>" — the item sits under a parent that has no CMS record, so it is left out until a run writes the parent: the parent waits for an admin's decision, was never written, was skipped or failed, or the source does not list it. <parent> names it as the Items page shows it, "Team (#25)" (MishkaCmsMigration.Workers.Helpers.Needs.parent/2). An admin's Import anyway (MishkaCmsMigration.MigrationItem's "parent_missing" answered with "import") writes it without its parent instead, noted "without_parent: <parent>".
  • a plugin's own reason, from migration:item:before_import; "plugins_cannot_run" while one of the plugins of that event, or of migration:content:convert for an item with a piece to convert, is not loaded; "plugins_failed" when one of them raised or answered something that is not the payload (MishkaCmsMigration.Events).
  • for a failure, what the source or the CMS action answered.

An item is :undecided when the import cannot place it safely on its own: its target found a conflict on the site (MishkaCmsMigration.Target's existing/2) that only an admin can settle, and no admin has answered it yet (MishkaCmsMigration.MigrationItem's decisions). Nothing is written for it, and every later dry run and import asks again until an admin decides. Its reason is the conflict:

  • "site_has_own" — the site already has a value of its own for the setting (MishkaCmsMigration.Targets.CmsSetting), or a stylesheet of its own with the item's name (MishkaCmsMigration.Targets.CmsStylesheet);
  • "menu_goes_to" — a menu that is not the site's main one waits for the admin to say where it goes, "menu_goes_to: <menu>" when a block menu has the same links as the classic menu <menu> (MishkaCmsMigration.Targets.CmsLayout);
  • "header_has_links" — the site layout's header already has links of its own, where a menu goes (MishkaCmsMigration.Targets.CmsLayout);
  • "footer_has_links" — the same for the footer's free slot, where a menu goes that the admin sent to the footer;
  • "component_name_taken" — the site, or a menu of the same run, already has a component with the name a menu sent to a component of its own would take ("component_written_first" while the other menu has not written it yet) (MishkaCmsMigration.Targets.CmsLayout);
  • "slug_taken" — the site already has a category or a tag with the item's slug, or a page at its path (MishkaCmsMigration.Targets.Helpers.BySlug, MishkaCmsMigration.Targets.CmsPage);
  • "blog_missing" — the item is of a kind the Blog writes, and the Blog is not installed (MishkaCmsMigration.Application.blog_kinds/0);
  • "player_not_allowed" — the item's content frames a YouTube or Vimeo player the site does not allow, and the admin who ran it may not allow it (MishkaCmsMigration.Targets.Helpers.Frames);
  • "draft_pages: <labels>" — the main menu links pages the site does not serve, as the drafts pages come in as, named by the links' labels (MishkaCmsMigration.Targets.CmsLayout): the conflict is the part before ": ";
  • "language_urls: <cause>" — the links the item writes would not follow the source's or the site's language addresses, <cause> being polylang, wpml or prefix_all (MishkaCmsMigration.Targets.Helpers.LanguageUrls).
  • "archived_in_cms" — an admin archived the CMS record the item was written or mapped to, so writing it, or anything that points at it, would be refused (MishkaCmsMigration.Workers.Helpers.Decision). Restore it brings the record back, and the item is updated, its note led by "restored"; what points at it waits meanwhile.
  • "deleted_in_cms" — the CMS record the item was written to was deleted for good, so anything that points at it would lose it (MishkaCmsMigration.Workers.Helpers.Decision). Import it again creates a new record, as a first import does, and the item remembers it, once: should the new record be deleted too, the admin is asked again; Leave it out skips it as "target_missing" from then on; what points at it waits meanwhile.

duplicates counts the media files that are the same bytes as an earlier one (MishkaCmsMigration.Targets.CmsMedia): found the ones the run met, merged those an admin decided to use the original for ("same_file_used", "same_file_used_no_redirect") and kept those decided to keep as their own ("same_file_kept"), the rest waiting for the decision ("same_file: <title of the original>", an :undecided item). A dry run downloads nothing and counts none.

guests counts the comments written, or planned, as a guest's ("guest_comment: <name>", possibly followed by "guest_email_dropped", the guest's email the Blog would refuse, left out of the comment) (MishkaCmsMigration.Targets.BlogComment).

An item a job of picked items met (MishkaCmsMigration.Workers.Helpers.Pipeline) waits, in a dry run and in an import, when what it points at has no CMS record yet:

  • "parent_undecided: <parent>", "parent_missing: <parent>" or "parent_not_listed: <id>" — its parent, which the job was not asked for, was never imported, as above;
  • "post_missing" — the post it points at, such as a comment's, was never imported;
  • "waits_for: <kinds>" — an item of another kind it points at, such as a post's category, was never imported, failed or waits for an admin's decision (MishkaCmsMigration.Target's needs/1), <kinds> naming those kinds, "category, tag".

A full run's item waits as "waits_for: <kinds>" too, for what it points at that waits for an admin's decision on its archived CMS record ("archived_in_cms"), or on one deleted for good ("deleted_in_cms").

counts and kinds count every item. kinds has a count for each kind that had at least one item, in the order the kinds were walked; a kind the source listed nothing of has none. entries are the last 1,000 items met (max_entries/0), in the order they were settled (an item that waits for its parent comes right after it), so a report stays small however large the site: the items left out number the sum of counts less the length of entries, and every item is still recorded (MishkaCmsMigration.MigrationItem).

A job of picked items does not replace its run's report: merge/2 puts what it did in it.

Summary

Types

One item's entry.

What happened, or would happen, to an item.

t()

Functions

report with entry added. Call done/1 once every entry is in.

report as it is saved: its last max_entries/0 entries, in the order they were added.

One item's entry: its kind, source id and title, its outcome and why.

How many entries a report keeps: the last ones added.

report, a run's saved report, with the entries of picked, the saved report of a job of the run's picked items, put in, in the order picked met them: each one replaces the entry of the same kind and source id, whose outcome it no longer counts, and is counted as add/2 counts it; one whose item the report does not list among its entries (the report never met it, or it is older than the last max_entries/0) is counted as one more. With no report yet, picked is the report.

A report with nothing in it.

The outcomes an entry may have.

The words an entry or a failed run keeps for why, at most 1000 characters: for an Ash error, each of its errors as field: message, translated with its values filled in (MishkaGervaz.Errors.translate_error/2, as the core's screens say them), and "forbidden" for a refusal by a policy; any other exception's message; a string as it is; an atom's name; guarded_struct's errors as field: message pairs; anything else inspected.

Types

entry()

@type entry() :: %{
  kind: String.t(),
  source_id: String.t() | nil,
  title: String.t() | nil,
  outcome: outcome(),
  reason: String.t() | nil
}

One item's entry.

outcome()

@type outcome() :: :created | :updated | :skipped | :failed | :undecided

What happened, or would happen, to an item.

t()

@type t() :: %{
  :counts => %{required(outcome()) => non_neg_integer()},
  :kinds => [map()],
  :entries => [entry()],
  :guests => non_neg_integer(),
  :duplicates => %{
    found: non_neg_integer(),
    merged: non_neg_integer(),
    kept: non_neg_integer()
  },
  optional(:kept) => non_neg_integer()
}

Functions

add(report, entry)

@spec add(t(), entry()) :: t()

report with entry added. Call done/1 once every entry is in.

done(report)

@spec done(t()) :: t()

report as it is saved: its last max_entries/0 entries, in the order they were added.

entry(kind, source_id, title, outcome, reason)

@spec entry(
  String.t(),
  String.t() | nil,
  String.t() | nil,
  outcome(),
  String.t() | nil
) :: entry()

One item's entry: its kind, source id and title, its outcome and why.

max_entries()

@spec max_entries() :: pos_integer()

How many entries a report keeps: the last ones added.

merge(report, picked)

@spec merge(map() | nil, t()) :: map()

report, a run's saved report, with the entries of picked, the saved report of a job of the run's picked items, put in, in the order picked met them: each one replaces the entry of the same kind and source id, whose outcome it no longer counts, and is counted as add/2 counts it; one whose item the report does not list among its entries (the report never met it, or it is older than the last max_entries/0) is counted as one more. With no report yet, picked is the report.

new()

@spec new() :: t()

A report with nothing in it.

outcomes()

@spec outcomes() :: [outcome()]

The outcomes an entry may have.

reason(error)

@spec reason(term()) :: String.t()

The words an entry or a failed run keeps for why, at most 1000 characters: for an Ash error, each of its errors as field: message, translated with its values filled in (MishkaGervaz.Errors.translate_error/2, as the core's screens say them), and "forbidden" for a refusal by a policy; any other exception's message; a string as it is; an atom's name; guarded_struct's errors as field: message pairs; anything else inspected.