card-mod-history 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []