Skip to content

Commit 82b2d56

Browse files
author
Job Hammer
committed
Fix specs and add content block versions and dashboard additions.
1 parent f7bc3de commit 82b2d56

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1338
-236
lines changed

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ group :development, :test, :ci, :linter do
2424
gem 'rubocop', require: false
2525
gem 'rubocop-performance', require: false
2626
gem 'rubocop-rails', require: false
27+
gem 'rubocop-rails-omakase', require: false
2728
gem 'rubocop-rspec', require: false
2829
gem 'rubocop-rspec_rails', require: false
2930
end

Gemfile.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,10 @@ GEM
325325
rack (>= 1.1)
326326
rubocop (>= 1.75.0, < 2.0)
327327
rubocop-ast (>= 1.44.0, < 2.0)
328+
rubocop-rails-omakase (1.1.0)
329+
rubocop (>= 1.72)
330+
rubocop-performance (>= 1.24)
331+
rubocop-rails (>= 2.30)
328332
rubocop-rspec (3.9.0)
329333
lint_roller (~> 1.1)
330334
rubocop (~> 1.81)
@@ -398,6 +402,7 @@ DEPENDENCIES
398402
rubocop
399403
rubocop-performance
400404
rubocop-rails
405+
rubocop-rails-omakase
401406
rubocop-rspec
402407
rubocop-rspec_rails
403408
ruby_cms!
@@ -509,6 +514,7 @@ CHECKSUMS
509514
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
510515
rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
511516
rubocop-rails (2.34.3) sha256=10d37989024865ecda8199f311f3faca990143fbac967de943f88aca11eb9ad2
517+
rubocop-rails-omakase (1.1.0) sha256=2af73ac8ee5852de2919abbd2618af9c15c19b512c4cfc1f9a5d3b6ef009109d
512518
rubocop-rspec (3.9.0) sha256=8fa70a3619408237d789aeecfb9beef40576acc855173e60939d63332fdb55e2
513519
rubocop-rspec_rails (2.32.0) sha256=4a0d641c72f6ebb957534f539d9d0a62c47abd8ce0d0aeee1ef4701e892a9100
514520
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33

app/controllers/ruby_cms/admin/base_controller.rb

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,21 @@ class BaseController < Rails.application.config.ruby_cms.admin_base_controller.c
2020
# new_session_path, root_path, etc.
2121
include RubyCms::Engine.routes.url_helpers
2222

23+
# Public API: dashboard block +data procs and host code may call this on the controller instance.
24+
def current_user_cms
25+
@current_user_cms ||= resolve_current_user
26+
end
27+
2328
private
2429

30+
def resolve_current_user
31+
if respond_to?(:current_user, true)
32+
send(:current_user)
33+
else
34+
Rails.application.config.ruby_cms.current_user_resolver&.call(self)
35+
end
36+
end
37+
2538
def require_cms_access
2639
ensure_authenticated
2740
require_permission!(:manage_admin)
@@ -67,18 +80,6 @@ def cms_redirect_path
6780
Rails.application.config.ruby_cms.unauthorized_redirect_path.presence || "/"
6881
end
6982

70-
def current_user_cms
71-
@current_user_cms ||= resolve_current_user
72-
end
73-
74-
def resolve_current_user
75-
if respond_to?(:current_user, true)
76-
send(:current_user)
77-
else
78-
Rails.application.config.ruby_cms.current_user_resolver&.call(self)
79-
end
80-
end
81-
8283
def render_not_found
8384
render "ruby_cms/errors/not_found",
8485
status: :not_found,
@@ -106,6 +107,8 @@ def set_cms_locale
106107
def model_param_key(model_class, param_name)
107108
params.key?(param_name) ? param_name : model_class.model_name.param_key.to_sym
108109
end
110+
111+
public :current_user_cms
109112
end
110113
end
111114
end
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# frozen_string_literal: true
2+
3+
module RubyCms
4+
module Admin
5+
class ContentBlockVersionsController < BaseController
6+
before_action { require_permission!(:manage_content_blocks) }
7+
before_action :set_content_block
8+
before_action :set_version, only: %i[show rollback]
9+
10+
def index
11+
@versions = @content_block.versions.reverse_chronologically.preloaded
12+
13+
respond_to do |format|
14+
format.html
15+
format.json { render json: versions_json }
16+
end
17+
end
18+
19+
def show
20+
@previous_version = @version.previous
21+
end
22+
23+
def rollback
24+
@content_block.rollback_to_version!(@version, user: current_user_cms)
25+
redirect_to ruby_cms_admin_content_block_versions_path(@content_block),
26+
notice: "Teruggedraaid naar versie #{@version.version_number}"
27+
end
28+
29+
private
30+
31+
def set_content_block
32+
@content_block = ContentBlock.find(params[:content_block_id])
33+
end
34+
35+
def set_version
36+
@version = @content_block.versions.find(params[:id])
37+
end
38+
39+
def versions_json
40+
@versions.map do |v|
41+
{
42+
id: v.id,
43+
version_number: v.version_number,
44+
event: v.event,
45+
user: display_user(v.user),
46+
created_at: v.created_at.strftime("%B %d, %Y at %I:%M %p"),
47+
metadata: v.metadata
48+
}
49+
end
50+
end
51+
52+
def display_user(user)
53+
return "System" if user.blank?
54+
55+
%i[email_address email username name].each do |attr|
56+
return user.public_send(attr) if user.respond_to?(attr) && user.public_send(attr).present?
57+
end
58+
user.respond_to?(:id) ? "User ##{user.id}" : "System"
59+
end
60+
end
61+
end
62+
end

app/controllers/ruby_cms/admin/dashboard_controller.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,53 @@
33
module RubyCms
44
module Admin
55
class DashboardController < BaseController
6+
# First main row: quick actions, recent errors, analytics (fixed keys). Remaining :main blocks render below (host/custom).
7+
PRIMARY_MAIN_ROW_KEYS = %i[quick_actions recent_errors analytics_overview].freeze
8+
69
def index
710
assign_counts
811
assign_recent_activity
12+
assign_analytics_overview_stats
13+
assign_dashboard_blocks
914
end
1015

1116
private
1217

18+
def assign_analytics_overview_stats
19+
start_date = 7.days.ago.beginning_of_day
20+
end_date = Time.current.end_of_day
21+
@dashboard_analytics_stats = RubyCms::Analytics::Report.new(start_date:, end_date:).dashboard_stats
22+
rescue StandardError => e
23+
Rails.logger.warn("[RubyCMS] Dashboard analytics snapshot: #{e.class}: #{e.message}")
24+
@dashboard_analytics_stats = nil
25+
end
26+
27+
def assign_dashboard_blocks
28+
visible = RubyCms.visible_dashboard_blocks(user: current_user_cms)
29+
@stats_blocks = visible
30+
.select {|b| b[:section] == :stats }
31+
.map {|b| prepare_dashboard_block(b) }
32+
main = visible
33+
.select {|b| b[:section] == :main }
34+
.map {|b| prepare_dashboard_block(b) }
35+
@primary_main_blocks = PRIMARY_MAIN_ROW_KEYS.filter_map {|k| main.find {|b| b[:key] == k } }
36+
@extra_main_blocks = main
37+
.reject {|b| PRIMARY_MAIN_ROW_KEYS.include?(b[:key]) }
38+
.sort_by {|b| [b[:order], b[:label].to_s] }
39+
end
40+
41+
def prepare_dashboard_block(block)
42+
from_data =
43+
if block[:data].respond_to?(:call)
44+
block[:data].call(self)
45+
else
46+
{}
47+
end
48+
from_data = {} unless from_data.is_a?(Hash)
49+
50+
block.merge(locals: { dashboard_block: block }.merge(from_data))
51+
end
52+
1353
def assign_counts
1454
@content_blocks_count = ::ContentBlock.count
1555
@content_blocks_published_count = ::ContentBlock.published.count
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
module RubyCms
4+
module Admin
5+
module DashboardHelper
6+
# Renders a dashboard block from the registry (+ :locals from the controller).
7+
def render_dashboard_block(block)
8+
locals = block[:locals]
9+
locals = {} if locals.nil?
10+
if block[:partial].present?
11+
render partial: block[:partial], locals: locals
12+
elsif block[:render].respond_to?(:call)
13+
block[:render].call(self, locals)
14+
else
15+
""
16+
end
17+
end
18+
end
19+
end
20+
end
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
static targets = ["panel", "list"]
5+
static values = {
6+
contentBlockId: Number
7+
}
8+
9+
connect() {
10+
this.isOpen = false
11+
}
12+
13+
async toggle() {
14+
if (this.isOpen) {
15+
this.close()
16+
} else {
17+
await this.open()
18+
}
19+
}
20+
21+
async open() {
22+
if (!this.contentBlockIdValue) return
23+
24+
try {
25+
const response = await fetch(
26+
`/admin/content_blocks/${this.contentBlockIdValue}/versions.json`,
27+
{ headers: { "Accept": "application/json" } }
28+
)
29+
if (!response.ok) throw new Error("Failed to fetch versions")
30+
31+
const versions = await response.json()
32+
this.renderVersions(versions)
33+
this.panelTarget.classList.remove("hidden")
34+
this.isOpen = true
35+
} catch (error) {
36+
console.error("Error loading version history:", error)
37+
}
38+
}
39+
40+
close() {
41+
this.panelTarget.classList.add("hidden")
42+
this.isOpen = false
43+
}
44+
45+
renderVersions(versions) {
46+
if (!versions.length) {
47+
this.listTarget.innerHTML = '<p class="text-sm text-muted-foreground p-3">Geen versies gevonden.</p>'
48+
return
49+
}
50+
51+
this.listTarget.innerHTML = versions.map(v => `
52+
<div class="flex items-center justify-between p-3 border-b border-border/40 last:border-0">
53+
<div>
54+
<span class="text-xs font-medium">v${v.version_number}</span>
55+
<span class="text-xs text-muted-foreground ml-1">${v.event}</span>
56+
<span class="text-xs text-muted-foreground ml-2">${v.user}</span>
57+
<span class="text-xs text-muted-foreground ml-2">${v.created_at}</span>
58+
</div>
59+
<button data-action="click->ruby-cms--content-block-history#rollback"
60+
data-version-id="${v.id}"
61+
class="text-xs text-primary hover:underline">
62+
Herstel
63+
</button>
64+
</div>
65+
`).join("")
66+
}
67+
68+
async rollback(event) {
69+
const versionId = event.currentTarget.dataset.versionId
70+
if (!confirm("Weet je zeker dat je deze versie wilt herstellen?")) return
71+
72+
try {
73+
const response = await fetch(
74+
`/admin/content_blocks/${this.contentBlockIdValue}/versions/${versionId}/rollback`,
75+
{
76+
method: "POST",
77+
headers: {
78+
"X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content,
79+
"Accept": "application/json"
80+
}
81+
}
82+
)
83+
if (!response.ok) throw new Error("Rollback failed")
84+
85+
window.location.reload()
86+
} catch (error) {
87+
console.error("Rollback error:", error)
88+
alert("Rollback mislukt")
89+
}
90+
}
91+
}

app/javascript/controllers/ruby_cms/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import LocaleTabsController from "ruby_cms/locale_tabs_controller";
1111
import ClickableRowController from "ruby_cms/clickable_row_controller";
1212
import AutoSavePreferenceController from "ruby_cms/auto_save_preference_controller";
1313
import NavOrderSortableController from "ruby_cms/nav_order_sortable_controller";
14+
import ContentBlockHistoryController from "ruby_cms/content_block_history_controller";
15+
1416

1517
export {
1618
VisualEditorController,
@@ -23,6 +25,7 @@ export {
2325
ClickableRowController,
2426
AutoSavePreferenceController,
2527
NavOrderSortableController,
28+
ContentBlockHistoryController,
2629
};
2730

2831
// Helper function to register all RubyCms controllers with a Stimulus application
@@ -37,6 +40,7 @@ export function registerRubyCmsControllers(application) {
3740
);
3841
application.register("ruby-cms--toggle", ToggleController);
3942
application.register("ruby-cms--locale-tabs", LocaleTabsController);
43+
application.register("ruby-cms--content-block-history", ContentBlockHistoryController);
4044
application.register("clickable-row", ClickableRowController);
4145
application.register(
4246
"ruby-cms--auto-save-preference",

0 commit comments

Comments
 (0)