Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions app/controllers/madmin/dashboard_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Madmin
class DashboardController < Madmin::ApplicationController
def show
@stats = {
projects_active: Project.active.count,
projects_archived: Project.where(status: "archived").count,
stories_pending: Story.pending.count,
stories_approved: Story.approved.count,
stories_rejected: Story.rejected.count,
estimates: Estimate.count,
users: User.count,
comments: Comment.count,
version_jumps: VersionJump.count
}

@recent_stories = Story.order(created_at: :desc).limit(5)
@recent_projects = Project.order(created_at: :desc).limit(5)
@recent_estimates = Estimate.includes(:story, :user).order(created_at: :desc).limit(5)
end
end
end
6 changes: 3 additions & 3 deletions app/madmin/resources/version_jump_resource.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
class VersionJumpResource < Madmin::Resource
# Attributes
attribute :id, form: false
attribute :technology
attribute :initial_version
attribute :target_version
attribute :technology, index: true
attribute :initial_version, index: true
attribute :target_version, index: true

# Associations
attribute :projects, form: false
Expand Down
16 changes: 12 additions & 4 deletions app/views/madmin/application/_navigation.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,18 @@
</div>
</div>

<div class="hidden md:flex flex-col flex-grow justify-between" data-dropdown-target="menu">
<% Madmin.resources.each do |resource| %>
<%= nav_link_to resource.friendly_name.pluralize, resource.index_path, class: "block p-2 rounded hover:bg-gray-100", starts_with: resource.index_path, active_class: "font-bold text-black" %>
<% end %>
<%# The dropdown controller is a mobile-only toggle. On connect it applies its
closed-state transition classes (opacity-0 scale-95), which would hide the
sidebar on desktop too. The md: overrides keep the menu fully visible on
desktop regardless of the dropdown's open/closed state (media-query rules
win over the controller's base classes). %>
<div class="hidden md:flex md:opacity-100 md:scale-100 flex-col flex-grow justify-between" data-dropdown-target="menu">
<div>
<%= nav_link_to "Dashboard", madmin_root_path, class: "block p-2 rounded hover:bg-gray-100", active_class: "font-bold text-black" %>
<% Madmin.resources.each do |resource| %>
<%= nav_link_to resource.friendly_name.pluralize, resource.index_path, class: "block p-2 rounded hover:bg-gray-100", starts_with: resource.index_path, active_class: "font-bold text-black" %>
<% end %>
</div>

<div class="mt-auto">
<%= link_to "View Madmin on GitHub", "https://github.com/excid3/madmin", target: :_blank, class: "block p-2 rounded text-gray-500 hover:bg-gray-100" %>
Expand Down
77 changes: 77 additions & 0 deletions app/views/madmin/dashboard/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<div class="md:flex justify-between items-center space-y-4 md:space-y-0">
<h1 class="text-xl font-semibold">Dashboard</h1>
</div>

<% tiles = [
{ label: "Active projects", value: @stats[:projects_active], path: madmin_projects_path },
{ label: "Archived projects", value: @stats[:projects_archived], path: madmin_projects_path },
{ label: "Pending stories", value: @stats[:stories_pending], path: madmin_stories_path },
{ label: "Approved stories", value: @stats[:stories_approved], path: madmin_stories_path },
{ label: "Rejected stories", value: @stats[:stories_rejected], path: madmin_stories_path },
{ label: "Estimates", value: @stats[:estimates], path: madmin_estimates_path },
{ label: "Users", value: @stats[:users], path: madmin_users_path },
{ label: "Comments", value: @stats[:comments], path: nil },
{ label: "Version jumps", value: @stats[:version_jumps], path: madmin_version_jumps_path }
] %>

<div class="mt-6 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">
<% tiles.each do |tile| %>
<% tag = tile[:path] ? :a : :div %>
<%= content_tag tag, (tile[:path] ? { href: tile[:path] } : {}).merge(class: "block p-4 border border-gray-200 rounded shadow-sm #{'hover:bg-gray-50 hover:border-indigo-400' if tile[:path]}") do %>
<span class="block text-2xl font-semibold text-indigo-600"><%= tile[:value] %></span>
<span class="block mt-1 text-sm text-gray-500"><%= tile[:label] %></span>
<% end %>
<% end %>
</div>

<div class="mt-8 grid grid-cols-1 lg:grid-cols-3 gap-6">
<div>
<h2 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Recent stories</h2>
<ul class="mt-3 divide-y divide-gray-100 border border-gray-200 rounded">
<% if @recent_stories.any? %>
<% @recent_stories.each do |story| %>
<li class="p-3">
<%= link_to story.title.presence || "Story ##{story.id}", madmin_story_path(story), class: "text-indigo-600 hover:underline" %>
<span class="block text-xs text-gray-400"><%= time_ago_in_words(story.created_at) %> ago</span>
</li>
<% end %>
<% else %>
<li class="p-3 text-sm text-gray-400">No stories yet.</li>
<% end %>
</ul>
</div>

<div>
<h2 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Recent projects</h2>
<ul class="mt-3 divide-y divide-gray-100 border border-gray-200 rounded">
<% if @recent_projects.any? %>
<% @recent_projects.each do |project| %>
<li class="p-3">
<%= link_to project.title.presence || "Project ##{project.id}", madmin_project_path(project), class: "text-indigo-600 hover:underline" %>
<span class="block text-xs text-gray-400"><%= time_ago_in_words(project.created_at) %> ago</span>
</li>
<% end %>
<% else %>
<li class="p-3 text-sm text-gray-400">No projects yet.</li>
<% end %>
</ul>
</div>

<div>
<h2 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Recent estimates</h2>
<ul class="mt-3 divide-y divide-gray-100 border border-gray-200 rounded">
<% if @recent_estimates.any? %>
<% @recent_estimates.each do |estimate| %>
<li class="p-3">
<%= link_to "Estimate ##{estimate.id}", madmin_estimate_path(estimate), class: "text-indigo-600 hover:underline" %>
<span class="block text-xs text-gray-400">
<%= estimate.story&.title.presence || "unknown story" %> &middot; <%= time_ago_in_words(estimate.created_at) %> ago
</span>
</li>
<% end %>
<% else %>
<li class="p-3 text-sm text-gray-400">No estimates yet.</li>
<% end %>
</ul>
</div>
</div>
31 changes: 31 additions & 0 deletions spec/requests/madmin/dashboard_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,36 @@
expect(response).to have_http_status(:ok)
expect(response.body).to include("madmin")
end

it "renders stat tiles instead of the old resource link grid" do
get "/madmin"

expect(response.body).to include("Active projects")
expect(response.body).to include("Pending stories")
expect(response.body).to include("Approved stories")
expect(response.body).to include("Estimates")
# The dashboard used to be a grid of links into each admin section,
# which duplicated the sidebar. That copy should be gone.
expect(response.body).not_to include("Jump to any admin section")
end

it "lists recent records with links to their madmin pages" do
story = FactoryBot.create(:story, title: "Recently added story")

get "/madmin"

expect(response.body).to include("Recent stories")
expect(response.body).to include("Recent projects")
expect(response.body).to include("Recent estimates")
expect(response.body).to include("Recently added story")
expect(response.body).to include(madmin_story_path(story))
end

it "renders the Dashboard link in the sidebar navigation" do
get "/madmin"

expect(response.body).to include("Dashboard")
expect(response.body).to include("href=\"#{madmin_root_path}\"")
end
end
end
12 changes: 11 additions & 1 deletion spec/requests/madmin/version_jumps_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
# exercises create/update in addition to the read-only actions.
RSpec.describe "Madmin version jumps", type: :request do
let(:admin) { FactoryBot.create(:user, :admin) }
let!(:version_jump) { FactoryBot.create(:version_jump) }
let!(:version_jump) do
FactoryBot.create(:version_jump, technology: "RailsUpgrade", initial_version: "6.1", target_version: "7.0")
end

before { login_as(admin, scope: :user) }
after { Warden.test_reset! }
Expand All @@ -15,6 +17,14 @@

expect(response).to have_http_status(:ok)
end

it "shows the technology and versions, not just the id" do
get "/madmin/version_jumps"

expect(response.body).to include("RailsUpgrade")
expect(response.body).to include("6.1")
expect(response.body).to include("7.0")
end
end

describe "GET /madmin/version_jumps/new" do
Expand Down
Loading