Merge pull request #1449 from basecamp/seed-new-accounts

System to seed new accounts
This commit is contained in:
Jorge Manrubia
2025-11-04 05:12:27 +01:00
committed by GitHub
24 changed files with 209 additions and 65 deletions
+1 -1
View File
@@ -296,7 +296,7 @@ GEM
logger (~> 1.6)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
lexxy (0.1.13.beta)
lexxy (0.1.14.beta)
rails (>= 8.0.2)
lint_roller (1.1.0)
logger (1.7.0)
-11
View File
@@ -231,17 +231,6 @@
text-align: center;
}
.events__welcome-video {
.events:has(.day-timeline-pagination-link) + & {
display: none !important;
}
iframe {
aspect-ratio: 16 / 9;
inline-size: 100%;
}
}
/* Event
/* ------------------------------------------------------------------------ */
+4
View File
@@ -163,6 +163,10 @@
z-index: 1;
}
:where(.lexxy-editor__toolbar-spacer) {
flex: 1;
}
/* Based on .input, .input--select */
.lexxy-code-language-picker {
-webkit-appearance: none;
@@ -61,6 +61,21 @@
display: none;
}
:where(hr) {
block-size: 0;
border-block-end: 1px solid currentColor;
inline-size: 20%;
margin: var(--block-margin) 0;
}
.horizontal-divider {
margin-inline: 0;
}
.lexxy-content__strikethrough {
text-decoration: line-through;
}
/* Code */
:where(code, pre) {
background-color: var(--color-canvas);
+1 -7
View File
@@ -1,5 +1,5 @@
class Account < ApplicationRecord
include Entropic
include Entropic, Seedeable
has_many_attached :uploads
@@ -25,12 +25,6 @@ class Account < ApplicationRecord
"/#{tenant}"
end
def setup_basic_template
user = User.first
Collection.create!(name: "Cards", creator: user, all_access: true)
end
private
def create_join_code
Account::JoinCode.create!
+7
View File
@@ -0,0 +1,7 @@
module Account::Seedeable
extend ActiveSupport::Concern
def setup_customer_template
Account::Seeder.new(self, User.active.first).seed
end
end
+93
View File
@@ -0,0 +1,93 @@
class Account::Seeder
attr_reader :account, :creator
def initialize(account, creator)
@account = account
@creator = creator
end
def seed
Current.set session: session do
populate
end
end
def seed!
raise "You can't run in production environments" unless Rails.env.local?
delete_everything
seed
end
private
def session
creator.identity.sessions.last
end
def populate
# ---------------
# Playground Collection
# ---------------
playground = Collection.create! name: "Playground", creator: creator, all_access: true
# Cards
playground.cards.create! creator: creator, title: "Watch this: Fizzy orientation video", status: "published", description: <<~HTML
<p>Theres a whole lot more you can do in Fizzy. In the video below 37signals founder and CEO, Jason Fried, will walk you through the basics in just 8 minutes.</p>
<p><br></p>
<action-text-attachment url="https://videos.37signals.com/fizzy/assets/videos/fizzyorientation-4k.mp4" caption="Fizzy orientation" content-type="video/mp4" filename="fizzyorientation-4k.mp4"></action-text-attachment>
HTML
playground.cards.create! creator: creator, title: "Grab the invite link to invite someone else", status: "published", description: <<~HTML
<p>Open Fizzy menu, select “<b><strong>+ Add people</b></strong>”, then copy the invite link. You can give this link to someone else so they can make an login for themselves in your account.</p>
HTML
playground.cards.create! creator: creator, title: "Head back home to check out activity", status: "published", description: <<~HTML
<p>Hit 1 or pull down the BOXCAR menu and select Home.</p>
HTML
playground.cards.create! creator: creator, title: "Check out all cards assigned to you", status: "published", description: <<~HTML
<p>Pull down the Fizzy menu at the top of the screen, and select “<b><strong>Assigned to me</b></strong>” or just hit “3” on your keyboard any time.</p>
HTML
playground.cards.create! creator: creator, title: "Open the Fizzy menu", status: "published", description: <<~HTML
<p>The Fizzy menu is how you get around the app. Click <b><strong>Fizzy</b></strong> at the top of the screen or hit the J key on your keyboard to pop it open.</p>
HTML
playground.cards.create! creator: creator, title: "Assign this card to yourself", status: "published", description: <<~HTML
<p>Click the little head with the + next to it, pick yourself.</p>
HTML
playground.cards.create! creator: creator, title: "Tag this card “Design” then move it to YES", status: "published", description: <<~HTML
<p>Click the little Tag icon, type design, then <b><strong>Create tag</b></strong>. Then, move the card to the new YES column you created in the previous step.</p>
HTML
playground.cards.create! creator: creator, title: "Make two more columns", status: "published", description: <<~HTML
<ol>
<li>Make one called "Yes"</li>
<li>Make another called "Working on"</li>
</ol>
<p><br></p>
<p>Go back to the Board view, click the little “+” to the right of the DONE column, name the column, pick a color, then do it again.</p>
<p><br></p>
<p>After that, drag this card to “DONE” or select “DONE” in the sidebar.</p>
HTML
playground.cards.create! creator: creator, title: "Move this card to NOT NOW", status: "published", description: <<~HTML
<p>You can either select NOT NOW over in the sidebar, or you can go back out to the board view and drag this card into the NOT NOW column on the left side.</p>
HTML
playground.cards.create! creator: creator, title: "Rename this card", status: "published", description: <<~HTML
<ol>
<li>Click the title and you can rename the card, change the description, or add more information to the card.</li>
<li>Then, hit "Mark as Done" at the bottom of the card.</li>
<li>Finally, hit “<b><strong>←Playground</strong></b>” in the top left of the screen to go back to the board.</li>
</ol>
HTML
end
def delete_everything
Current.set session: session do
Collection.destroy_all
end
end
end
-7
View File
@@ -11,11 +11,4 @@ class Collection < ApplicationRecord
scope :alphabetically, -> { order("lower(name)") }
scope :ordered_by_recently_accessed, -> { merge(Access.ordered_by_recently_accessed) }
after_destroy :ensure_default_collection
private
def ensure_default_collection
Collection.create!(name: "Cards") if Collection.none?
end
end
+16
View File
@@ -28,6 +28,22 @@ module Attachments
attachments.any?
end
def remote_images
@remote_images ||= rich_text_record&.body&.attachables&.grep(ActionText::Attachables::RemoteImage) || []
end
def has_remote_images?
remote_images.any?
end
def remote_videos
@remote_videos ||= rich_text_record&.body&.attachables&.grep(ActionText::Attachables::RemoteVideo) || []
end
def has_remote_videos?
remote_videos.any?
end
private
def rich_text_record
@rich_text_record ||= begin
@@ -0,0 +1,8 @@
<figure class="attachment attachment--preview">
<%= image_tag remote_image.url, width: remote_image.width, height: remote_image.height %>
<% if caption = remote_image.try(:caption) %>
<figcaption class="attachment__caption">
<%= caption %>
</figcaption>
<% end %>
</figure>
@@ -0,0 +1,10 @@
<figure class="attachment attachment--preview attachment--video">
<%= tag.video controls: true, width: remote_video.width, height: remote_video.height do %>
<%= tag.source src: remote_video.url, type: remote_video.content_type %>
<% end %>
<% if caption = remote_video.try(:caption) %>
<figcaption class="attachment__caption">
<%= caption %>
</figcaption>
<% end %>
</figure>
+5 -6
View File
@@ -1,8 +1,7 @@
<% if Current.user.collections.many? %>
<%= form_with model: collection, class: "txt-align-center margin-block-start-auto", method: :delete do |form| %>
<%= form.button class: "btn txt-negative borderless txt-small", data: { turbo_confirm: "Are you sure you want to permanently delete this board?" } do %>
<%= icon_tag "trash" %>
<span>Delete this board</span>
<% end %>
<%= form_with model: collection, class: "txt-align-center margin-block-start-auto", method: :delete do |form| %>
<%= form.button class: "btn txt-negative borderless txt-small", data: { turbo_confirm: "Are you sure you want to permanently delete this board?" } do %>
<%= icon_tag "trash" %>
<span>Delete this board</span>
<% end %>
<% end %>
+4 -16
View File
@@ -1,19 +1,7 @@
<% if eventable&.has_attachments? %>
<% if eventable&.has_attachments? || eventable&.has_remote_images? || eventable&.has_remote_videos? %>
<span class="event_attachments margin-block-half flex align-center gap-half">
<% eventable.attachments.each do |attachment| %>
<% variant = Attachments::VARIANTS[:small] %>
<% width = attachment.metadata["width"] %>
<% height = attachment.metadata["height"] %>
<% if attachment.previewable? %>
<%= image_tag url_for(attachment.preview(variant)), class: "attachment attachment--image", width: width, height: height %>
<% elsif attachment.variable? %>
<%= image_tag url_for(attachment.variant(variant)), class: "attachment attachment--image", width: width, height: height %>
<% else %>
<div class="attachment attachment--file attachment--<%= attachment.filename.extension -%>">
<span class="attachment__icon"><%= attachment.filename.extension&.downcase.presence || "unknown" %></span>
</div>
<% end %>
<% end %>
<%= render partial: "events/event/attachments/attachment", collection: eventable.attachments %>
<%= render partial: "events/event/attachments/remote_image", collection: eventable.remote_images %>
<%= render partial: "events/event/attachments/remote_video", collection: eventable.remote_videos %>
</span>
<% end %>
@@ -0,0 +1,13 @@
<% variant = Attachments::VARIANTS[:small] %>
<% width = attachment.metadata["width"] %>
<% height = attachment.metadata["height"] %>
<% if attachment.previewable? %>
<%= image_tag url_for(attachment.preview(variant)), class: "attachment attachment--image", width: width, height: height %>
<% elsif attachment.variable? %>
<%= image_tag url_for(attachment.variant(variant)), class: "attachment attachment--image", width: width, height: height %>
<% else %>
<div class="attachment attachment--file attachment--<%= attachment.filename.extension -%>">
<span class="attachment__icon"><%= attachment.filename.extension&.downcase.presence || "unknown" %></span>
</div>
<% end %>
@@ -0,0 +1 @@
<%= image_tag remote_image.url, class: "attachment attachment--image", width: remote_image.width, height: remote_image.height %>
@@ -0,0 +1,3 @@
<%= tag.video controls: true, class: "attachment attachment--video", width: remote_video.width, height: remote_video.height do %>
<%= tag.source src: remote_video.url, type: remote_video.content_type %>
<% end %>
-10
View File
@@ -23,13 +23,3 @@
<%= day_timeline_pagination_link(@day_timeline, @filter) %>
<% end %>
<% end %>
<div class="events__welcome-video flex justify-center">
<iframe
frameborder="0"
src="https://www.youtube.com/embed/kAgUQbD9Wc0?si=IIjKjUIk5PchL0-a"
title="Fizzy Beta orientation"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen></iframe>
</div>
-1
View File
@@ -1,5 +1,4 @@
<%= collapsible_nav_section "Jump to…" do %>
<%= filter_place_menu_item "https://www.youtube.com/watch?v=rNKRdk7DLHM", "Fizzy beta orientation", "youtube", new_window: true %>
<%= filter_place_menu_item account_settings_path, "Account Settings", "settings" %>
<%= filter_place_menu_item user_path(Current.user), "My Profile", "person" %>
<%= filter_place_menu_item notifications_path, "Notifications", "bell" %>
-1
View File
@@ -27,7 +27,6 @@ def create_tenant(signal_account_name)
membership: membership
}
)
account.setup_basic_template
end
ApplicationRecord.current_tenant = tenant_id
+1 -1
View File
@@ -116,7 +116,7 @@ class Signup
}
)
@user = User.find_by!(role: :admin)
@account.setup_basic_template
@account.setup_customer_template
end
end
+1 -1
View File
@@ -54,7 +54,7 @@ class SignupTest < ActiveSupport::TestCase
end
test "#complete" do
Account.any_instance.expects(:setup_basic_template).once
Account.any_instance.expects(:setup_customer_template).once
# First create the membership
signup_for_membership = Signup.new(
+11
View File
@@ -0,0 +1,11 @@
namespace :seed do
desc "Seed customer data for a specific account (e.g: rails seed:customer ARTENANT=1234)"
task :customer, [ :tenant_id ] => "db:tenant" do |t, args|
raise "Please provide a tenant ID: rails seed:customer ARTENANT=1234" unless ApplicationRecord.current_tenant
account = Account.sole
Account::Seeder.new(account, User.active.first).seed!
puts "✓ Seeded account #{account.name} (tenant: #{account.id})"
end
end
-3
View File
@@ -68,9 +68,6 @@ Current.set(
email_address: owner_email
}
)
# Setup basic template
account.setup_basic_template
end
puts "✓ Tenant created"
+15
View File
@@ -0,0 +1,15 @@
require "test_helper"
class Account::SedeableTest < ActiveSupport::TestCase
setup do
@account = Account.sole
end
test "setup_customer_template adds collections, cards, and comments" do
assert_changes -> { Collection.count } do
assert_changes -> { Card.count } do
@account.setup_customer_template
end
end
end
end