Class: Ecoportal::API::GraphQL::Diff::VersionDiff

Inherits:
Object
  • Object
show all
Defined in:
lib/ecoportal/api/graphql/diff/version_diff.rb

Overview

Self-version diff: two snapshots of the SAME page/template (same Mongo ids).

Because the object keeps its ids across versions, pairing is EXACT (match by id) — no equivalence matching needed. This is the "what did this ticket change to the UAT template" tool, and the basis of a portable changelog ("commit") that — once id-translated through a pairing map — can be replayed onto PROD. Cross-object diff (UAT<->PROD, page<->template) is a different, harder problem handled by the pairing engine (not here).

Operates on the raw page/template doc (the JSON the gem returns for a page/template), so it is a shared, session-less capability usable by admin/troubleshooting/integration/QA alike.

diff = Diff::VersionDiff.new(before_doc, after_doc) diff.changes # => [Change, ...] diff.summary # => { added:, removed:, changed:, moved:, total: } diff.changelog # => ["~ field 'Severity' ...", ...]

MODALITIES — pass a Diff::Strategy to select a scope / move-sensitivity. The default strategy (id-paired, structural, move-aware, changelog) reproduces the pre-Phase-4 behaviour exactly, so VersionDiff.new(before, after).changes is unchanged. A narrower scope (:config_only, :data_migration) or a move-insensitive strategy filters the SAME computed change-set — the pairing is still by-id here (VersionDiff is the :id pairing front-end). Cross-object pairings (:genome/:type_label/:assisted) are handled by CrossObjectDiff.

Constant Summary collapse

BYTYPE_CONFIG =

Typed field-configuration properties that this diff EMITS, keyed by the field's __typename. Each entry maps a byType input key (the sub-hash the schema's editFieldConfiguration.byType accepts) to the list of doc property names that make up that sub-hash. Only pairs where the READ property name matches the WRITE (byType) input key EXACTLY are listed here — this diff never fabricates a config change it cannot map back to a real command input.

Deliberately conservative: e.g. PlainText carries multiline in the read model but the plainText byType input has no multiline key (it exposes maxLength), so PlainText config is NOT emitted here (it would be unmappable). Likewise required has no editFieldConfiguration key at all, so a required flip is not a config change we emit.

{
  'Gauge'  => { gauge:  %w[max] },
  'Select' => { select: %w[dataType multiple flat other otherDesc] },
  'Date'   => { date:   %w[showTime pastOnly todayButton] }
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(before_doc, after_doc, strategy: Strategy.default) ⇒ VersionDiff

Returns a new instance of VersionDiff.



45
46
47
48
49
# File 'lib/ecoportal/api/graphql/diff/version_diff.rb', line 45

def initialize(before_doc, after_doc, strategy: Strategy.default)
  @before   = before_doc || {}
  @after    = after_doc  || {}
  @strategy = strategy || Strategy.default
end

Instance Method Details

#changelogObject



69
70
71
# File 'lib/ecoportal/api/graphql/diff/version_diff.rb', line 69

def changelog
  changes.map(&:description)
end

#changesObject

The change-set, filtered by the strategy's scope + move-sensitivity. The full set is computed once (all_changes); the strategy decides what survives. The default strategy keeps everything (the historical behaviour).



54
55
56
# File 'lib/ecoportal/api/graphql/diff/version_diff.rb', line 54

def changes
  @changes ||= @strategy.filter(all_changes)
end

#summaryObject



58
59
60
61
62
63
64
65
66
67
# File 'lib/ecoportal/api/graphql/diff/version_diff.rb', line 58

def summary
  by_op = changes.group_by(&:op)
  {
    added:   by_op.fetch(:added, []).size,
    removed: by_op.fetch(:removed, []).size,
    changed: by_op.fetch(:changed, []).size,
    moved:   by_op.fetch(:moved, []).size,
    total:   changes.size
  }
end

#to_hObject



73
74
75
# File 'lib/ecoportal/api/graphql/diff/version_diff.rb', line 73

def to_h
  { summary: summary, changes: changes.map(&:to_h) }
end