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
7 changes: 7 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ RUN rm -rf node_modules
# ---------- Runtime stage ----------
FROM base

# Application version for display in the UI (admin sidebar + site footers).
# Computed on the host from `git describe --tags --always` and passed in by
# the deploy tooling (see config/deploy.yml `builder.args`). The `.git` dir is
# excluded from the build context (.dockerignore), so it cannot be derived here.
ARG APP_VERSION
ENV APP_VERSION=$APP_VERSION

# Copy built artifacts
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
Expand Down
3 changes: 1 addition & 2 deletions app/components/directory/footer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ def render_impressum
data_nosnippet: true) do
span { "#{t('colophon.year', year: Time.zone.today.year)} #{t('colophon.copyright')}" }
span do
build = ENV['GIT_REV'] ? ENV['GIT_REV'][0, 7] : 'main'
plain 'Build: '
link_to(build, "https://github.com/geeksforsocialchange/PlaceCal/commit/#{build}",
link_to(AppVersion.label(fallback: 'main'), AppVersion.url,
class: 'text-tertiary underline hover:decoration-primary')
end
end
Expand Down
3 changes: 1 addition & 2 deletions app/components/footer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,9 @@ def render_impressum
plain t('colophon.address')
end
p do
build = ENV['GIT_REV'] ? ENV['GIT_REV'][0, 7] : 'main'
plain 'Build: '
tag.tt do
link_to(build, "https://github.com/geeksforsocialchange/PlaceCal/commit/#{build}")
link_to(AppVersion.label(fallback: 'main'), AppVersion.url)
end
end
end
Expand Down
59 changes: 59 additions & 0 deletions app/lib/app_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

# Derives a human-friendly application version for display in the UI.
#
# The version is injected at Docker build time via the +APP_VERSION+ env var
# (see Dockerfile / config/deploy.yml), where it is set from
# `git describe --tags --always` (e.g. "v0.27.3" on a tagged release, or
# "v0.27.3-115-gabc1234" when ahead of the latest tag).
#
# Fallback chain for the displayed label:
# 1. ENV['APP_VERSION'] — the git-describe version
# 2. ENV['GIT_REV'][0, 7] — short commit SHA (legacy)
# 3. the supplied fallback — 'dev' (admin) or 'main' (public footers)
class AppVersion
REPO_URL = 'https://github.com/geeksforsocialchange/PlaceCal'

# @param fallback [String] label to use when no version/commit is available
# @return [String] the version label to display
def self.label(fallback: 'dev')
app_version || git_rev_short || fallback
end

# @return [String] a GitHub URL appropriate for the current version:
# - the release-tag page when APP_VERSION is set
# - the commit diff when only GIT_REV is available
# - the repository home otherwise
def self.url
if (version = app_version)
"#{REPO_URL}/releases/tag/#{tag_for(version)}"
elsif (rev = git_rev)
"#{REPO_URL}/commit/#{rev}"
else
REPO_URL
end
end

def self.app_version
value = ENV.fetch('APP_VERSION', nil)
value.presence
end

def self.git_rev
value = ENV.fetch('GIT_REV', nil)
value.presence
end

def self.git_rev_short
git_rev&.slice(0, 7)
end

# Extract the leading tag from a `git describe` string so the release link
# stays valid even when the build is ahead of the latest tag.
# "v0.27.3-115-gabc1234" => "v0.27.3"; "v0.27.3" => "v0.27.3"
def self.tag_for(version)
version.sub(/-\d+-g[0-9a-f]+\z/, '')
end

private_class_method :app_version, :git_rev, :git_rev_short, :tag_for
end
5 changes: 2 additions & 3 deletions app/views/layouts/admin/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,9 @@ def leftbar_build_info
icon(:code, size: '3')
plain "#{t('admin.leftbar.build')}: "
code(class: 'font-mono') do
git_rev = ENV.fetch('GIT_REV', nil)
link_to(
git_rev ? git_rev[0, 7] : 'dev',
"https://github.com/geeksforsocialchange/PlaceCal/commit/#{git_rev}",
AppVersion.label(fallback: 'dev'),
AppVersion.url,
class: 'text-placecal-teal-dark underline hover:no-underline'
)
end
Expand Down
5 changes: 5 additions & 0 deletions config/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,5 +81,10 @@ builder:
arch: amd64
args:
RUBY_VERSION: <%= File.read('.ruby-version').strip %>
# Friendly version from the latest git tag (e.g. v0.27.3), computed on the
# host because the .git dir is excluded from the Docker build context.
# Baked into the image as ENV APP_VERSION by the Dockerfile, so it reaches
# runtime without a separate env var. Falls back to the short commit SHA.
APP_VERSION: <%= `git describe --tags --always 2>/dev/null`.strip %>

# Database migrations run via bin/docker-entrypoint (db:prepare)
54 changes: 54 additions & 0 deletions spec/components/admin/leftbar_build_info_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

require "rails_helper"

# Covers the build-info widget in the admin sidebar
# (Views::Layouts::Admin::Application#leftbar_build_info). The surrounding
# layout needs an authenticated controller, so we exercise just the build-info
# fragment via a thin Phlex subclass.
RSpec.describe Views::Layouts::Admin::Application, type: :component do
# Renders only the build-info fragment (a private method on the layout).
let(:build_info_component) do
Class.new(described_class) do
def view_template
leftbar_build_info
end
end
end

context "when APP_VERSION is set" do
before do
stub_const("ENV", ENV.to_hash.merge("APP_VERSION" => "v0.9.1"))
end

it "shows the version label linking to the release tag" do
render_inline(build_info_component.new)

link = page.find("a", text: "v0.9.1")
expect(link[:href])
.to eq("https://github.com/geeksforsocialchange/PlaceCal/releases/tag/v0.9.1")
end

it "keeps the Build label" do
render_inline(build_info_component.new)

expect(page).to have_text(I18n.t("admin.leftbar.build"))
end
end

context "when neither APP_VERSION nor GIT_REV is set" do
before do
env = ENV.to_hash
env.delete("APP_VERSION")
env.delete("GIT_REV")
stub_const("ENV", env)
end

it "falls back to a 'dev' build label linking to the repo" do
render_inline(build_info_component.new)

link = page.find("a", text: "dev")
expect(link[:href]).to eq("https://github.com/geeksforsocialchange/PlaceCal")
end
end
end
35 changes: 35 additions & 0 deletions spec/components/directory/footer_component_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Components::Directory::Footer, type: :component do
context "when APP_VERSION is set" do
before do
stub_const("ENV", ENV.to_hash.merge("APP_VERSION" => "v0.9.1"))
end

it "shows the version label linking to the release tag" do
render_inline(described_class.new)

link = page.find("a", text: "v0.9.1")
expect(link[:href])
.to eq("https://github.com/geeksforsocialchange/PlaceCal/releases/tag/v0.9.1")
end
end

context "when neither APP_VERSION nor GIT_REV is set" do
before do
env = ENV.to_hash
env.delete("APP_VERSION")
env.delete("GIT_REV")
stub_const("ENV", env)
end

it "falls back to a 'main' build label linking to the repo" do
render_inline(described_class.new)

link = page.find("a", text: "main")
expect(link[:href]).to eq("https://github.com/geeksforsocialchange/PlaceCal")
end
end
end
35 changes: 35 additions & 0 deletions spec/components/footer_component_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe Components::Footer, type: :component do
context "when APP_VERSION is set" do
before do
stub_const("ENV", ENV.to_hash.merge("APP_VERSION" => "v0.9.1"))
end

it "shows the version label linking to the release tag" do
render_inline(described_class.new(nil))

link = page.find("a", text: "v0.9.1")
expect(link[:href])
.to eq("https://github.com/geeksforsocialchange/PlaceCal/releases/tag/v0.9.1")
end
end

context "when neither APP_VERSION nor GIT_REV is set" do
before do
env = ENV.to_hash
env.delete("APP_VERSION")
env.delete("GIT_REV")
stub_const("ENV", env)
end

it "falls back to a 'main' build label linking to the repo" do
render_inline(described_class.new(nil))

link = page.find("a", text: "main")
expect(link[:href]).to eq("https://github.com/geeksforsocialchange/PlaceCal")
end
end
end
63 changes: 63 additions & 0 deletions spec/lib/app_version_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# frozen_string_literal: true

require "rails_helper"

RSpec.describe AppVersion do
describe ".label" do
it "returns APP_VERSION when set" do
stub_const("ENV", ENV.to_hash.merge("APP_VERSION" => "v0.27.3", "GIT_REV" => "abcdef1234567"))
expect(described_class.label).to eq("v0.27.3")
end

it "falls back to the short GIT_REV when APP_VERSION is unset" do
env = ENV.to_hash.merge("GIT_REV" => "abcdef1234567")
env.delete("APP_VERSION")
stub_const("ENV", env)
expect(described_class.label).to eq("abcdef1")
end

it "falls back to the given fallback when neither is set" do
env = ENV.to_hash
env.delete("APP_VERSION")
env.delete("GIT_REV")
stub_const("ENV", env)
expect(described_class.label(fallback: "dev")).to eq("dev")
expect(described_class.label(fallback: "main")).to eq("main")
end

it "treats a blank APP_VERSION as unset" do
stub_const("ENV", ENV.to_hash.merge("APP_VERSION" => "", "GIT_REV" => "abcdef1234567"))
expect(described_class.label).to eq("abcdef1")
end
end

describe ".url" do
it "links to the release tag page when APP_VERSION is an exact tag" do
stub_const("ENV", ENV.to_hash.merge("APP_VERSION" => "v0.27.3"))
expect(described_class.url)
.to eq("https://github.com/geeksforsocialchange/PlaceCal/releases/tag/v0.27.3")
end

it "strips the git-describe suffix when building the release tag link" do
stub_const("ENV", ENV.to_hash.merge("APP_VERSION" => "v0.27.3-115-g0b8e07e8"))
expect(described_class.url)
.to eq("https://github.com/geeksforsocialchange/PlaceCal/releases/tag/v0.27.3")
end

it "links to the commit diff when only GIT_REV is set" do
env = ENV.to_hash.merge("GIT_REV" => "abcdef1234567")
env.delete("APP_VERSION")
stub_const("ENV", env)
expect(described_class.url)
.to eq("https://github.com/geeksforsocialchange/PlaceCal/commit/abcdef1234567")
end

it "links to the repository home when nothing is set" do
env = ENV.to_hash
env.delete("APP_VERSION")
env.delete("GIT_REV")
stub_const("ENV", env)
expect(described_class.url).to eq("https://github.com/geeksforsocialchange/PlaceCal")
end
end
end