card-mod-history 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,98 @@
1
+ def acted_at
2
+ last_act.acted_at
3
+ end
4
+
5
+ def revised_at
6
+ (last_action && (act = last_action.act) && act.acted_at) || Time.zone.now
7
+ end
8
+
9
+ def last_change_on field, opts={}
10
+ action_id = extract_action_id(opts[:before] || opts[:not_after])
11
+
12
+ # If there is only one action then there are no entries in the changes table,
13
+ # so we can't do a sql search but the changes are accessible via the action.
14
+ if no_last_change? action_id, opts[:before]
15
+ nil
16
+ elsif create_action_last_change? action_id
17
+ create_action&.change field
18
+ else
19
+ last_change_from_action_id action_id, field, opts
20
+ end
21
+ end
22
+
23
+ def no_last_change? action_id, before
24
+ before && action_id == create_action.id
25
+ end
26
+
27
+ def create_action_last_change? action_id
28
+ action_id == create_action&.id || (!action_id && create_action&.sole?)
29
+ end
30
+
31
+ def last_change_from_action_id action_id, field, opts
32
+ Change.joins(:action).where(
33
+ last_change_sql_conditions(opts),
34
+ card_id: id,
35
+ action_id: action_id,
36
+ field: Card::Change.field_index(field)
37
+ ).order(:id).last
38
+ end
39
+
40
+ def last_change_sql_conditions opts
41
+ cond = "card_actions.card_id = :card_id AND field = :field"
42
+ cond += " AND (draft is not true)" unless opts[:including_drafts]
43
+ operator = "<" if opts[:before]
44
+ operator = "<=" if opts[:not_after]
45
+ cond += " AND card_action_id #{operator} :action_id" if operator
46
+ cond
47
+ end
48
+
49
+ def last_action_id
50
+ last_action&.id
51
+ end
52
+
53
+ def last_action
54
+ actions.where("id IS NOT NULL").last
55
+ end
56
+
57
+ def last_content_action
58
+ last_change_on(:db_content)&.action
59
+ end
60
+
61
+ def last_content_action_id
62
+ last_change_on(:db_content)&.card_action_id
63
+ end
64
+
65
+ def last_actor
66
+ last_act.actor
67
+ end
68
+
69
+ def last_act
70
+ @last_act ||=
71
+ if (action = last_action)
72
+ last_act_on_self = acts.last
73
+ act_of_last_action = action.act
74
+ return act_of_last_action unless last_act_on_self
75
+ return last_act_on_self unless act_of_last_action
76
+
77
+ return last_act_on_self if act_of_last_action == last_act_on_self
78
+
79
+ if last_act_on_self.acted_at > act_of_last_action.acted_at
80
+ last_act_on_self
81
+ else
82
+ act_of_last_action
83
+ end
84
+ end
85
+ end
86
+
87
+ def previous_action action_id
88
+ return unless action_id
89
+
90
+ action_index = actions.find_index { |a| a.id == action_id }
91
+ all_actions[action_index - 1] if action_index.to_i.nonzero?
92
+ end
93
+
94
+ private
95
+
96
+ def extract_action_id action_arg
97
+ action_arg.is_a?(Card::Action) ? action_arg.id : action_arg
98
+ end
@@ -0,0 +1,65 @@
1
+ def revision action, before_action=false
2
+ # a "revision" refers to the state of all tracked fields
3
+ # at the time of a given action
4
+ action = Card::Action.fetch(action) if action.is_a? Integer
5
+ return unless action
6
+
7
+ if before_action
8
+ revision_before_action action
9
+ else
10
+ revision_attributes action
11
+ end
12
+ end
13
+
14
+ def revision_attributes action
15
+ Card::Change::TRACKED_FIELDS.each_with_object({}) do |field, attr_changes|
16
+ last_change = action.change(field) || last_change_on(field, not_after: action)
17
+ attr_changes[field.to_sym] = (last_change ? last_change.value : self[field])
18
+ end
19
+ end
20
+
21
+ def revision_before_action action
22
+ if (prev_action = action.previous_action)
23
+ revision prev_action
24
+ else
25
+ { trash: true }
26
+ end
27
+ end
28
+
29
+ def rollback_request?
30
+ history? && actions_to_revert.any?
31
+ end
32
+
33
+ def process_revert_actions revert_actions=nil
34
+ revert_actions ||= actions_to_revert
35
+ update_args = { subcards: {} }
36
+ reverting_to_previous = Env.params["revert_to"] == "previous"
37
+ revert_actions.each do |action|
38
+ merge_revert_action! action, update_args, reverting_to_previous
39
+ end
40
+ update_args
41
+ end
42
+
43
+ def actions_to_revert
44
+ if (act_id = Env.params["revert_act"])
45
+ Act.find(act_id).actions
46
+ else
47
+ explicit_actions_to_revert
48
+ end
49
+ end
50
+
51
+ def explicit_actions_to_revert
52
+ Array.wrap(Env.params["revert_actions"]).map do |a_id|
53
+ Action.fetch(a_id) || nil
54
+ end.compact
55
+ end
56
+
57
+ def merge_revert_action! action, update_args, reverting_to_previous
58
+ rev = action.card.revision(action, reverting_to_previous)
59
+ rev.delete :name unless rev[:name] # handles null name field in compound cards
60
+ if action.card_id == id
61
+ update_args.merge! rev
62
+ else
63
+ update_args[:subcards][action.card.name] = rev
64
+ end
65
+ end
@@ -0,0 +1,64 @@
1
+ # if these aren't in a nested module, the methods just overwrite the base
2
+ # methods, but we need a distinct module so that super will be able to refer to
3
+ # the base methods.
4
+ def content
5
+ @selected_action_id ? selected_content : super
6
+ end
7
+
8
+ def content= value
9
+ @selected_content = nil
10
+ super
11
+ end
12
+
13
+ def select_action_by_params params
14
+ action = nth_action(params[:rev]) || action_from_id(params[:rev_id])
15
+ return unless action
16
+
17
+ select_action action.id
18
+ end
19
+
20
+ def select_action action_id
21
+ run_callbacks :select_action do
22
+ self.selected_action_id = action_id
23
+ end
24
+ end
25
+
26
+ def selected_action_id
27
+ @selected_action_id || (@current_action&.id) || last_action_id
28
+ end
29
+
30
+ def selected_action_id= action_id
31
+ @selected_content = nil
32
+ @selected_action_id = action_id
33
+ end
34
+
35
+ def selected_action
36
+ selected_action_id && Action.fetch(selected_action_id)
37
+ end
38
+
39
+ def selected_content
40
+ @selected_content ||= content_at_time_of_selected_action || db_content
41
+ end
42
+
43
+ def content_at_time_of_selected_action
44
+ lc = last_change_on(:db_content, not_after: @selected_action_id, including_drafts: true)
45
+ lc&.value
46
+ end
47
+
48
+ def with_selected_action_id action_id
49
+ current_action_id = @selected_action_id
50
+ select_action_id action_id
51
+ result = yield
52
+ select_action_id current_action_id
53
+ result
54
+ end
55
+
56
+ def select_action_id action_id
57
+ run_callbacks :select_action do
58
+ self.selected_action_id = action_id
59
+ end
60
+ end
61
+
62
+ def selected_content_action_id
63
+ @selected_action_id || new_content_action_id || last_content_action_id
64
+ end
@@ -0,0 +1,35 @@
1
+ # History views
2
+
3
+ format :html do
4
+ view :history, cache: :never do
5
+ frame do
6
+ class_up "d0-card-body", "history-slot"
7
+ acts_layout card.history_acts, :relative, :show
8
+ end
9
+ end
10
+
11
+ view :act, cache: :never do
12
+ act_listing act_from_context
13
+ end
14
+
15
+ view :act_legend do
16
+ bs_layout do
17
+ row md: [12, 12], lg: [7, 5] do
18
+ col action_legend
19
+ col content_legend, class: "text-right"
20
+ end
21
+ end
22
+ end
23
+
24
+ view :draft_legend do
25
+ "#{action_icon(:draft)} unsaved draft"
26
+ end
27
+
28
+ view :action_summary do
29
+ action_content action_from_context, :summary
30
+ end
31
+
32
+ view :action_expanded do
33
+ action_content action_from_context, :expanded
34
+ end
35
+ end
@@ -0,0 +1,65 @@
1
+ format :html do
2
+ view :creator_credit,
3
+ wrap: { div: { class: "text-muted creator-credit" } }, cache: :never do
4
+ return "" unless card.real?
5
+ "Created by #{nest card.creator, view: :link} "\
6
+ "#{time_ago_in_words(card.created_at)} ago"
7
+ end
8
+
9
+ view :updated_by, wrap: { div: { class: "text-muted" } } do
10
+ return "" unless card.id
11
+ updaters = Card.search(updater_of: { id: card.id })
12
+ return "" unless updaters.present?
13
+
14
+ updaters = updater_links updaters, others_target: Card.fetch(card, :editors)
15
+ "Updated by #{updaters}"
16
+ end
17
+
18
+ def updater_links updaters, item_view: :link, max_count: 3, others_target: card
19
+ return "" unless updaters.present?
20
+
21
+ total = updaters.size
22
+ fetch_count = total > max_count ? max_count - 1 : max_count
23
+
24
+ reduced = first_card(fetch_count).map { |c| nest c, view: item_view }
25
+ if total > max_count
26
+ reduced << link_to_card(others_target, "#{total - fetch_count} others")
27
+ end
28
+ reduced.to_sentence
29
+ end
30
+
31
+ def acts_bridge_layout acts, context=:bridge
32
+ output [
33
+ _render_creator_credit,
34
+ act_link_list(acts, context),
35
+ act_paging(acts, context)
36
+ ]
37
+ end
38
+
39
+ def act_link_list acts, context
40
+ items = acts_for_accordion(acts, context) do |act, seq|
41
+ act_link_list_item act, seq, context
42
+ end
43
+ bridge_pills items
44
+ end
45
+
46
+ def act_link_list_item act, seq=nil, _context=nil
47
+ opts = act_listing_opts_from_params(seq)
48
+ opts[:slot_class] = "revision-#{act.id} history-slot nav-item"
49
+ act_renderer(:bridge).new(self, act, opts).bridge_link
50
+ end
51
+
52
+ def act_list_group acts, context, &block
53
+ list_group acts_for_accordion(acts, context, &block), class: "clear-both"
54
+ end
55
+
56
+ view :bridge_act, cache: :never do
57
+ opts = act_listing_opts_from_params(nil)
58
+ act = act_from_context
59
+ ar = act_renderer(:bridge).new(self, act, opts)
60
+ class_up "action-list", "my-3"
61
+ wrap_with_overlay title: ar.overlay_title, slot: breadcrumb_data("History") do
62
+ act_listing(act, opts[:act_seq], :bridge)
63
+ end
64
+ end
65
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: card-mod-history
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.11.0
5
+ platform: ruby
6
+ authors:
7
+ - Ethan McCutchen
8
+ - Philipp Kühl
9
+ - Gerry Gleason
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2020-12-24 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: card
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.101.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - '='
27
+ - !ruby/object:Gem::Version
28
+ version: 1.101.0
29
+ description: ''
30
+ email:
31
+ - info@decko.org
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - lib/card/act.rb
37
+ - lib/card/act/act_renderer.rb
38
+ - lib/card/act/act_renderer/absolute_act_renderer.rb
39
+ - lib/card/act/act_renderer/bridge_act_renderer.rb
40
+ - lib/card/act/act_renderer/relative_act_renderer.rb
41
+ - lib/card/action.rb
42
+ - lib/card/action/action_renderer.rb
43
+ - lib/card/action/admin.rb
44
+ - lib/card/action/differ.rb
45
+ - lib/card/change.rb
46
+ - set/all/history.rb
47
+ - set/all/history/act_listing.rb
48
+ - set/all/history/actions.rb
49
+ - set/all/history/acts.rb
50
+ - set/all/history/events.rb
51
+ - set/all/history/last.rb
52
+ - set/all/history/revision.rb
53
+ - set/all/history/selected.rb
54
+ - set/all/history/views.rb
55
+ - set/all/history_bridge.rb
56
+ homepage: http://decko.org
57
+ licenses:
58
+ - GPL-3.0
59
+ metadata:
60
+ card-mod: history
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '2.5'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.0.3
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: Revision histories in acts, actions, and changes
80
+ test_files: []