card-mod-history 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/card/act.rb +137 -0
- data/lib/card/act/act_renderer.rb +217 -0
- data/lib/card/act/act_renderer/absolute_act_renderer.rb +34 -0
- data/lib/card/act/act_renderer/bridge_act_renderer.rb +53 -0
- data/lib/card/act/act_renderer/relative_act_renderer.rb +59 -0
- data/lib/card/action.rb +230 -0
- data/lib/card/action/action_renderer.rb +94 -0
- data/lib/card/action/admin.rb +36 -0
- data/lib/card/action/differ.rb +89 -0
- data/lib/card/change.rb +70 -0
- data/set/all/history.rb +109 -0
- data/set/all/history/act_listing.rb +124 -0
- data/set/all/history/actions.rb +124 -0
- data/set/all/history/acts.rb +8 -0
- data/set/all/history/events.rb +100 -0
- data/set/all/history/last.rb +98 -0
- data/set/all/history/revision.rb +65 -0
- data/set/all/history/selected.rb +64 -0
- data/set/all/history/views.rb +35 -0
- data/set/all/history_bridge.rb +65 -0
- metadata +80 -0
@@ -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: []
|