Merge branch 'main' into consolidate-cards

* main: (94 commits)
  Source order seems to be significant
  Use `card.title`
  Nicer flow
  Fix string interpolation
  Fix constant
  Use record lookup
  Always use sole
  Explained less and extracted nothing of value
  Weird double negative
  Remove anemic and indirect method that explained nothing
  Can use record lookup for workflows
  Access control has already been done through the card
  Style
  Use _path in all cases where we are not potentially changing the domain
  Clearer
  Spacing
  Get rid of card_title
  Join the two users controllers but split out role setting
  Ensure user is returned
  We are not using the parameter
  ...
This commit is contained in:
Andy Smith
2025-04-14 09:40:22 -05:00
144 changed files with 586 additions and 1183 deletions
@@ -1,10 +1,10 @@
class Accounts::JoinCodesController < ApplicationController
def show
render svg: RQRCode::QRCode.new(join_url(Current.account.join_code)).as_svg(viewbox: true, fill: :white, color: :black)
render svg: RQRCode::QRCode.new(join_url(Account.sole.join_code)).as_svg(viewbox: true, fill: :white, color: :black)
end
def update
Current.account.reset_join_code
redirect_to account_users_path
Account.sole.reset_join_code
redirect_to users_path
end
end
@@ -1,31 +0,0 @@
class Accounts::UsersController < ApplicationController
before_action :set_user, only: %i[ update destroy ]
before_action :ensure_permission_to_administer_user, only: %i[ update destroy ]
def index
@users = Current.account.users.active
end
def update
@user.update(role_params)
redirect_to account_users_path
end
def destroy
@user.deactivate
redirect_to account_users_path
end
private
def set_user
@user = Current.account.users.active.find(params[:id])
end
def ensure_permission_to_administer_user
head :forbidden unless Current.user.can_administer?(@user)
end
def role_params
{ role: params.require(:user)[:role].presence_in(%w[ member admin ]) || "member" }
end
end
@@ -1,12 +0,0 @@
class Cards::BoostsController < ApplicationController
include CardScoped
def create
count = if params[:boost_count].to_i == @card.boosts_count
@card.boosts_count + 1
else
params[:boost_count].to_i
end
@card.boost!(count)
end
end
@@ -1,42 +1,38 @@
class Cards::Comments::ReactionsController < ApplicationController
include CardScoped
before_action :set_comment
def index
@reactions = @comment.reactions.ordered.includes(:reacter)
end
def new
end
def create
@reaction = @comment.reactions.create!(reaction_params)
reaction = @comment.reactions.create!(params.expect(reaction: :content))
broadcast_create
redirect_to card_comment_reactions_url(@comment.card, @comment)
broadcast_create(reaction)
redirect_to card_comment_reactions_path(@card, @comment)
end
def destroy
@reaction = @comment.reactions.find(params[:id])
@reaction.destroy!
reaction = @comment.reactions.find(params[:id])
reaction.destroy
broadcast_remove
broadcast_remove(reaction)
end
private
def set_comment
@comment = Current.account.comments.find(params[:comment_id])
@comment = Comment.belonging_to_card(@card).find(params[:comment_id])
end
def reaction_params
params.require(:reaction).permit(:content)
def broadcast_create(reaction)
reaction.broadcast_append_to @card, target: [ @comment, :reactions ], partial: "cards/comments/reactions/reaction"
end
def broadcast_create
@reaction.broadcast_append_to @reaction.comment, :comments,
target: "reactions_comment_#{@comment.id}", partial: "cards/comments/reactions/reaction", locals: { comment: @comment }
end
def broadcast_remove
@reaction.broadcast_remove_to @reaction.comment, :comments
def broadcast_remove(reaction)
reaction.broadcast_remove_to @card
end
end
+11 -16
View File
@@ -1,10 +1,11 @@
class Cards::CommentsController < ApplicationController
include CardScoped
before_action :set_comment, only: [ :show, :edit, :update, :destroy ]
before_action :require_own_comment, only: [ :edit, :update, :destroy ]
before_action :set_comment, only: %i[ show edit update destroy ]
before_action :ensure_creatorship, only: %i[ edit update destroy ]
def create
@card.capture new_comment
@card.capture Comment.new(comment_params)
end
def show
@@ -23,21 +24,15 @@ class Cards::CommentsController < ApplicationController
end
private
def comment_params
params.require(:comment).permit(:body)
end
def new_comment
Comment.new(comment_params)
end
def set_comment
@comment = Comment.joins(:message)
.where(messages: { card_id: @card.id })
.find(params[:id])
@comment = Comment.belonging_to_card(@card).find(params[:id])
end
def require_own_comment
head :forbidden unless Current.user == @comment.creator
def ensure_creatorship
head :forbidden if Current.user != @comment.creator
end
def comment_params
params.expect(comment: :body)
end
end
@@ -1,5 +1,6 @@
class Cards::PreviewsController < ApplicationController
include FilterScoped
before_action :set_filter, only: :index
def index
+9 -2
View File
@@ -1,11 +1,18 @@
class Cards::StagingsController < ApplicationController
include CardScoped
before_action :set_stage
def create
if params[:stage_id].present?
@card.toggle_stage Current.account.stages.find(params[:stage_id])
if @stage
@card.toggle_stage @stage
else
@card.update!(stage: nil)
end
end
private
def set_stage
@stage = Workflow::Stage.find_by(id: params[:stage_id])
end
end
+1 -1
View File
@@ -2,7 +2,7 @@ class Cards::TaggingsController < ApplicationController
include CardScoped
def new
@tags = Current.account.tags.alphabetically
@tags = Tag.all.alphabetically
end
def create
+3 -6
View File
@@ -15,7 +15,8 @@ class CardsController < ApplicationController
end
def create
redirect_to @collection.cards.create!
card = @collection.cards.create!
redirect_to card
end
def show
@@ -26,7 +27,7 @@ class CardsController < ApplicationController
def destroy
@card.destroy!
redirect_to cards_path(collection_ids: [ @card.collection ]), notice: deleted_notice
redirect_to cards_path(collection_ids: [ @card.collection ]), notice: ("Card deleted" unless @card.creating?)
end
def update
@@ -50,8 +51,4 @@ class CardsController < ApplicationController
def card_params
params.expect(card: [ :status, :title, :color, :due_on, :image, :draft_comment, tag_ids: [] ])
end
def deleted_notice
"Card deleted" unless @card.creating?
end
end
@@ -5,12 +5,11 @@ class Collections::WorkflowsController < ApplicationController
def update
@collection.update! workflow: @workflow
redirect_to cards_path(collection_ids: [ @collection ])
end
private
def set_workflow
@workflow = Current.account.workflows.find(params.expect(collection: [ :workflow_id ]).require(:workflow_id))
@workflow = Workflow.find(params.expect(collection: [ :workflow_id ]).require(:workflow_id))
end
end
+3 -3
View File
@@ -2,11 +2,11 @@ class CollectionsController < ApplicationController
before_action :set_collection, except: %i[ new create ]
def new
@collection = Current.account.collections.build
@collection = Collection.new
end
def create
@collection = Current.account.collections.create! collection_params
@collection = Collection.create! collection_params
redirect_to cards_path(collection_ids: [ @collection ])
end
@@ -37,7 +37,7 @@ class CollectionsController < ApplicationController
end
def grantees
Current.account.users.active.where id: grantee_ids
User.active.where id: grantee_ids
end
def revokees
+1 -1
View File
@@ -40,7 +40,7 @@ module Authentication
def request_authentication
session[:return_to_after_authenticating] = request.url
redirect_to new_session_url
redirect_to new_session_path
end
def after_authentication_url
+1 -1
View File
@@ -11,6 +11,6 @@ module CardScoped
end
def set_collection
@collection = Current.user.collections.find(@card.collection_id)
@collection = @card.collection
end
end
+6 -10
View File
@@ -19,13 +19,9 @@ module EventsTimeline
end
def events_for_activity_day
user_events.where(created_at: @activity_day.all_day).
group_by { |event| [ event.created_at.hour, helpers.event_column(event) ] }.
map { |hour_col, events|
[ hour_col,
events.uniq { |e| e.action == "boosted" ? [ e.creator_id, e.card_id ] : e.id }
]
}
user_events.where(created_at: @activity_day.all_day)
.group_by { |event| [ event.created_at.hour, helpers.event_column(event) ] }
.map { |hour_col, events| [ hour_col, events.uniq { |e| e.id } ] }
end
def latest_event_before_activity_day
@@ -33,12 +29,12 @@ module EventsTimeline
end
def user_events
Event.where(card: user_cards, creator: Current.account.users)
Event.where(card: user_cards)
end
def user_cards
Current.user.accessible_cards
.published_or_drafted_by(Current.user)
.where(collection_id: collection_filter)
.published_or_drafted_by(Current.user)
.where(collection_id: collection_filter)
end
end
+1 -1
View File
@@ -7,6 +7,6 @@ module WorkflowScoped
private
def set_workflow
@workflow = Current.account.workflows.find params[:workflow_id]
@workflow = Workflow.find(params[:workflow_id])
end
end
+6 -9
View File
@@ -8,7 +8,12 @@ class FiltersController < ApplicationController
def destroy
@filter.destroy!
redirect_after_destroy
if request.referer == root_url
redirect_to root_path
else
redirect_to cards_path(@filter.as_params)
end
end
private
@@ -19,12 +24,4 @@ class FiltersController < ApplicationController
def filter_params
params.permit(*Filter::PERMITTED_PARAMS).compact_blank
end
def redirect_after_destroy
if request.referer == root_url
redirect_to root_path
else
redirect_to cards_path(@filter.as_params)
end
end
end
+2 -6
View File
@@ -7,17 +7,13 @@ class FirstRunsController < ApplicationController
end
def create
user = FirstRun.create!(user_params)
user = FirstRun.create!(params.expect(user: [ :name, :email_address, :password ]))
start_new_session_for user
redirect_to root_path
end
private
def prevent_repeats
redirect_to root_path unless Account.none?
end
def user_params
params.expect(user: [ :name, :email_address, :password ])
redirect_to root_path if Account.any?
end
end
+1 -4
View File
@@ -2,10 +2,7 @@ class QrCodesController < ApplicationController
allow_unauthenticated_access
def show
qr_code_link = QrCodeLink.from_signed(params[:id])
svg = RQRCode::QRCode.new(qr_code_link.url).as_svg(viewbox: true, fill: :white, color: :black)
expires_in 1.year, public: true
render plain: svg, content_type: "image/svg+xml"
render svg: RQRCode::QRCode.new(QrCodeLink.from_signed(params[:id]).url).as_svg(viewbox: true, fill: :white, color: :black)
end
end
+1 -6
View File
@@ -1,14 +1,9 @@
class TerminalsController < ApplicationController
def show
@events = Event.where(card: user_cards, creator: Current.user).chronologically.reverse_order.limit(20)
@events = Event.where(card: Current.user.accessible_cards, creator: Current.user).chronologically.reverse_order.limit(20)
end
def edit
@filter = Current.user.filters.from_params params.permit(*Filter::PERMITTED_PARAMS)
end
private
def user_cards
Current.user.accessible_cards
end
end
+2 -1
View File
@@ -5,7 +5,8 @@ class UploadsController < ApplicationController
before_action :set_attachment, only: :show
def create
@upload = Current.account.uploads_attachments.create! blob: create_blob!
# FIXME: Try to get upload attachments on root
@upload = Account.sole.uploads_attachments.create! blob: create_blob!
end
def show
+1 -1
View File
@@ -24,7 +24,7 @@ class Users::AvatarsController < ApplicationController
end
def set_user
@user = Current.account.users.find(params[:user_id])
@user = User.find(params[:user_id])
end
def render_avatar_or_initials
+22
View File
@@ -0,0 +1,22 @@
class Users::RolesController < ApplicationController
before_action :set_user
before_action :ensure_permission_to_administer_user
def update
@user.update(role_params)
redirect_to users_path
end
private
def set_user
@user = User.active.find(params[:user_id])
end
def ensure_permission_to_administer_user
head :forbidden unless Current.user.can_administer?(@user)
end
def role_params
{ role: params.require(:user)[:role].presence_in(%w[ member admin ]) || "member" }
end
end
+20 -6
View File
@@ -1,15 +1,20 @@
class UsersController < ApplicationController
require_unauthenticated_access only: %i[ new create ]
before_action :set_user, only: %i[ show edit update ]
before_action :set_user, only: %i[ show edit update destroy ]
before_action :set_account_from_join_code, only: %i[ new create ]
before_action :ensure_permission_to_administer_user, only: %i[ update destroy ]
def index
@users = User.active
end
def new
@user = @account.users.build
@user = User.new
end
def create
user = @account.users.create!(user_params)
user = User.create!(user_params)
start_new_session_for user
redirect_to root_path
end
@@ -21,8 +26,13 @@ class UsersController < ApplicationController
end
def update
@user.update user_params
redirect_to user_path(@user)
@user.update! user_params
redirect_to @user
end
def destroy
@user.deactivate
redirect_to users_path
end
private
@@ -31,7 +41,11 @@ class UsersController < ApplicationController
end
def set_user
@user = Current.account.users.active.find(params[:id])
@user = User.active.find(params[:id])
end
def ensure_permission_to_administer_user
head :forbidden unless Current.user.can_administer?(@user)
end
def user_params
@@ -9,7 +9,7 @@ class Workflows::StagesController < ApplicationController
def create
@stage = @workflow.stages.create! stage_params
redirect_to workflow_path(@workflow)
redirect_to @workflow
end
def edit
@@ -17,12 +17,12 @@ class Workflows::StagesController < ApplicationController
def update
@stage.update! stage_params
redirect_to workflow_path(@workflow)
redirect_to @workflow
end
def destroy
@stage.destroy
redirect_to workflow_path(@workflow)
redirect_to @workflow
end
private
+6 -6
View File
@@ -2,15 +2,15 @@ class WorkflowsController < ApplicationController
before_action :set_workflow, only: %i[ show edit update destroy ]
def index
@workflows = Current.account.workflows
@workflows = Workflow.all
end
def new
@workflow = Current.account.workflows.new
@workflow = Workflow.new
end
def create
@workflow = Current.account.workflows.create! workflow_params
@workflow = Workflow.create! workflow_params
# FIXME: this should definitely change.
[ "Triage", "In progress", "On Hold", "Review" ].each { |name| @workflow.stages.create! name: name }
redirect_to workflows_path
@@ -24,7 +24,7 @@ class WorkflowsController < ApplicationController
def update
@workflow.update! workflow_params
redirect_to workflow_path(@workflow)
redirect_to @workflow
end
def destroy
@@ -34,10 +34,10 @@ class WorkflowsController < ApplicationController
private
def set_workflow
@workflow = Current.account.workflows.find params[:id]
@workflow = Workflow.find params[:id]
end
def workflow_params
params.expect workflow: [ :name ]
params.expect workflow: :name
end
end
+1 -4
View File
@@ -4,9 +4,6 @@ module ApplicationHelper
end
def icon_tag(name, **options)
classes = class_names "icon icon--#{name}", options.delete(:class)
options["aria-hidden"] = true
content_tag :span, "", class: classes, **options
tag.span class: class_names("icon icon--#{name}", options.delete(:class)), "aria-hidden": true, **options
end
end
-12
View File
@@ -1,16 +1,4 @@
module CardsHelper
CARD_ROTATION = %w[ 75 60 45 35 25 5 ]
def card_title(card)
card.title.presence || "Untitled"
end
def card_rotation(card)
value = CARD_ROTATION[Zlib.crc32(card.to_param) % CARD_ROTATION.size]
"--card-rotate: #{value}deg;"
end
def cards_next_page_link(target, page:, filter:, fetch_on_visible: false, data: {}, **options)
url = cards_previews_path(target: target, page: page.next_param, **filter.as_params)
-5
View File
@@ -1,9 +1,4 @@
module CommentsHelper
def comment_tag(comment, &)
tag.div id: dom_id(comment), class: "comment flex align-start full-width",
data: { creator_id: comment.creator_id, created_by_current_user_target: "creation" }, &
end
def new_comment_placeholder(card)
if card.creator == Current.user && card.messages.comments.empty?
"Next, add some notes, context, pictures, or video about this…"
+12 -16
View File
@@ -66,32 +66,30 @@ module EventsHelper
case event.action
when "assigned"
if event.assignees.include?(Current.user)
"#{ event.creator == Current.user ? "You" : event.creator.name } will handle <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } will handle <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
else
"#{ event.creator == Current.user ? "You" : event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } assigned #{ event.assignees.pluck(:name).to_sentence } to <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
end
when "unassigned"
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
when "boosted"
"#{ event.creator == Current.user ? "You" : event.creator.name } boosted <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } unassigned #{ event.assignees.include?(Current.user) ? "yourself" : event.assignees.pluck(:name).to_sentence } from <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
when "commented"
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } commented on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
when "published"
"#{ event.creator == Current.user ? "You" : event.creator.name } added <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } added <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
when "closed"
"#{ event.creator == Current.user ? "You" : event.creator.name } closed <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{ event.creator == Current.user ? "You" : event.creator.name } closed <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
when "staged"
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ card_title(event.card) }</span> to the #{event.stage_name} stage".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.card.title }</span> to the #{event.stage_name} stage".html_safe
when "unstaged"
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ card_title(event.card) }</span> out ofthe #{event.stage_name} stage".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} moved <span style='color: var(--card-color)'>#{ event.card.title }</span> out ofthe #{event.stage_name} stage".html_safe
when "due_date_added"
"#{event.creator == Current.user ? "You" : event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} set the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
when "due_date_changed"
"#{event.creator == Current.user ? "You" : event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} changed the date to #{event.particulars.dig('particulars', 'due_date').to_date.strftime('%B %-d')} on <span style='color: var(--card-color)'>#{ event.card.title }</span>".html_safe
when "due_date_removed"
"#{event.creator == Current.user ? "You" : event.creator.name} removed the date on <span style='color: var(--card-color)'>#{ card_title(event.card) }</span>"
"#{event.creator == Current.user ? "You" : event.creator.name} removed the date on <span style='color: var(--card-color)'>#{ event.card.title }</span>"
when "title_changed"
"#{event.creator == Current.user ? "You" : event.creator.name} renamed <span style='color: var(--card-color)'>#{ card_title(event.card) }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
"#{event.creator == Current.user ? "You" : event.creator.name} renamed <span style='color: var(--card-color)'>#{ event.card.title }</span> (was: '#{event.particulars.dig('particulars', 'old_title')})'".html_safe
end
end
@@ -99,8 +97,6 @@ module EventsHelper
case event.action
when "assigned"
"assigned"
when "boosted"
"thumb-up"
when "staged"
"bolt"
when "unstaged"
+1 -1
View File
@@ -2,7 +2,7 @@ module MessagesHelper
def messages_tag(card, &)
turbo_frame_tag dom_id(card, :messages),
class: "comments gap center",
style: "--card-color: <%= card.color %>",
style: "--card-color: #{card.color}",
role: "group", aria: { label: "Messages" },
data: {
controller: "created-by-current-user",
+2 -4
View File
@@ -1,11 +1,9 @@
module NotificationsHelper
def notification_title(notification)
title = card_title(notification.card)
if notification.resource.is_a? Comment
"RE: " + title
"RE: " + notification.card.title
elsif notification_event_action(notification) == "assigned"
"Assigned to you: " + title
"Assigned to you: " + notification.card.title
else
title
end
+1 -17
View File
@@ -1,21 +1,5 @@
class Account < ApplicationRecord
include ClosureReasons, Joinable
has_many :collections, dependent: :destroy
has_many :cards, through: :collections
has_many :users, dependent: :destroy do
def system
find_or_create_system_user(proxy_association.owner)
end
end
has_many :comments, through: :users
has_many :workflows, dependent: :destroy
has_many :stages, through: :workflows, class_name: "Workflow::Stage"
has_many :tags, dependent: :destroy
include Joinable
has_many_attached :uploads
end
-29
View File
@@ -1,29 +0,0 @@
module Account::ClosureReasons
extend ActiveSupport::Concern
DEFAULT_LABELS = [
"Completed",
"Duplicate",
"Maybe later",
"Working as intended"
]
FALLBACK_LABEL = "Done"
included do
has_many :closure_reasons, dependent: :destroy, class_name: "Closure::Reason" do
def labels
pluck(:label).presence || [ FALLBACK_LABEL ]
end
end
after_create :create_default_closure_reasons
end
private
def create_default_closure_reasons
DEFAULT_LABELS.each do |label|
closure_reasons.create! label: label
end
end
end
+5 -3
View File
@@ -1,5 +1,5 @@
class Card < ApplicationRecord
include Assignable, Boostable, Colored, Commentable, Engageable, Eventable, Golden,
include Assignable, Colored, DraftCommenting, Engageable, Eventable, Golden,
Messages, Notifiable, Pinnable, Closeable, Scorable, Searchable, Staged,
Statuses, Taggable, Watchable
@@ -22,8 +22,6 @@ class Card < ApplicationRecord
scope :indexed_by, ->(index) do
case index
when "most_active" then ordered_by_activity
when "most_discussed" then ordered_by_comments
when "most_boosted" then ordered_by_boosts
when "newest" then reverse_chronologically
when "oldest" then chronologically
when "latest" then latest
@@ -39,6 +37,10 @@ class Card < ApplicationRecord
end
end
def title=(new_title)
self[:title] = new_title.presence || "Untitled"
end
def cache_key
[ super, collection&.name ].compact.join("/")
end
-18
View File
@@ -1,18 +0,0 @@
module Card::Boostable
extend ActiveSupport::Concern
included do
scope :ordered_by_boosts, -> { order boosts_count: :desc }
end
def boost!(count = 1)
count = count.to_i
count = 1 if count < 1
transaction do
track_event :boosted, count: count
update! boosts_count: count
rescore
end
end
end
+2 -2
View File
@@ -16,7 +16,7 @@ module Card::Closeable
class_methods do
def auto_close_all_due
due_to_be_closed.find_each do |card|
card.close(user: card.collection.account.users.system, reason: "Closed")
card.close(user: User.system, reason: "Closed")
end
end
end
@@ -41,7 +41,7 @@ module Card::Closeable
closure&.created_at
end
def close(user: Current.user, reason: Account::ClosureReasons::FALLBACK_LABEL)
def close(user: Current.user, reason: Closure::Reason::FALLBACK_LABEL)
unless closed?
transaction do
create_closure! user: user, reason: reason
-20
View File
@@ -1,20 +0,0 @@
module Card::Commentable
extend ActiveSupport::Concern
included do
scope :ordered_by_comments, -> { order comments_count: :desc }
end
def comment_created(comment)
increment! :comments_count
watch_by comment.creator
track_event :commented, comment_id: comment.id
rescore
end
def comment_destroyed
decrement! :comments_count
rescore
end
end
+33
View File
@@ -0,0 +1,33 @@
module Card::DraftCommenting
extend ActiveSupport::Concern
included do
after_save :capture_draft_comment
end
def draft_comment
find_or_build_initial_comment.body.content
end
def draft_comment=(body)
if body.present?
@draft_comment = body
else
messages.comments.destroy_all
end
end
private
def find_or_build_initial_comment
message = messages.comments.first || messages.new(messageable: Comment.new)
message.comment
end
def capture_draft_comment
if @draft_comment.present?
find_or_build_initial_comment.update! body: @draft_comment, creator: creator
end
@draft_comment = nil
end
end
+6 -6
View File
@@ -10,14 +10,14 @@ module Card::Eventable
touch :last_active_at
end
private
def track_event(action, creator: Current.user, **particulars)
if published?
event = find_or_capture_event_summary.events.create! action: action, creator: creator, card: self, particulars: particulars
event.generate_notifications_later
end
def track_event(action, creator: Current.user, **particulars)
if published?
event = find_or_capture_event_summary.events.create! action: action, creator: creator, card: self, particulars: particulars
event.generate_notifications_later
end
end
private
def find_or_capture_event_summary
transaction do
messages.last&.event_summary || capture(EventSummary.new).event_summary
-26
View File
@@ -3,35 +3,9 @@ module Card::Messages
included do
has_many :messages, -> { chronologically }, dependent: :destroy
after_save :capture_draft_comment
end
def capture(messageable)
messages.create! messageable: messageable
end
def draft_comment
find_or_build_initial_comment.body.content
end
def draft_comment=(body)
if body.present?
@draft_comment = body
else
messages.comments.destroy_all
end
end
private
def find_or_build_initial_comment
message = messages.comments.first || messages.new(messageable: Comment.new)
message.comment
end
def capture_draft_comment
if @draft_comment.present?
find_or_build_initial_comment.update! body: @draft_comment, creator: creator
end
@draft_comment = nil
end
end
+1 -2
View File
@@ -36,7 +36,6 @@ module Card::Scorable
def event_weight(event)
case
when event.boosted? then 1
when event.comment&.first_by_author_on_card? then 20
when event.comment&.follows_comment_by_another_author? then 15
when event.commented? then 10
@@ -60,6 +59,6 @@ module Card::Scorable
end
def scorable_events
events.where(action: [ :commented, :boosted ])
events.where(action: :commented)
end
end
+1 -1
View File
@@ -9,7 +9,7 @@ module Card::Taggable
end
def toggle_tag_with(title)
tag = collection.account.tags.find_or_create_by!(title: title)
tag = Tag.find_or_create_by!(title: title)
transaction do
if tagged_with?(tag)
+20 -1
View File
@@ -1,3 +1,22 @@
class Closure::Reason < ApplicationRecord
belongs_to :account
DEFAULT_LABELS = [
"Completed",
"Duplicate",
"Maybe later",
"Working as intended"
]
FALLBACK_LABEL = "Done"
class << self
def labels
pluck(:label).presence || [ FALLBACK_LABEL ]
end
def create_defaults
DEFAULT_LABELS.each do |label|
create! label: label
end
end
end
end
-1
View File
@@ -1,7 +1,6 @@
class Collection < ApplicationRecord
include Accessible, Broadcastable, Filterable
belongs_to :account
belongs_to :creator, class_name: "User", default: -> { Current.user }
belongs_to :workflow, optional: true
+1 -1
View File
@@ -34,6 +34,6 @@ module Collection::Accessible
private
def grant_access_to_everyone
accesses.grant_to(account.users) if all_access_previously_changed?(to: true)
accesses.grant_to(User.all) if all_access_previously_changed?(to: true)
end
end
+1 -1
View File
@@ -3,6 +3,6 @@ module Collection::Broadcastable
included do
broadcasts_refreshes
broadcasts_refreshes_to ->(collection) { [ collection.account, :collections ] }
broadcasts_refreshes_to ->(_) { :collections }
end
end
+16 -25
View File
@@ -1,43 +1,34 @@
class Comment < ApplicationRecord
include Messageable, Notifiable, Searchable
include Messageable, Searchable
belongs_to :creator, class_name: "User", default: -> { Current.user }
has_many :reactions, dependent: :destroy
searchable_by :body_plain_text, using: :comments_search_index, as: :body
has_many :reactions, dependent: :delete_all
has_markdown :body
searchable_by :body_plain_text, using: :comments_search_index, as: :body
before_destroy :cleanup_events
# FIXME: Not a fan of this. Think all references to comment should come directly from the message.
scope :belonging_to_card, ->(card) { joins(:message).where(messages: { card_id: card.id }) }
def first_by_author_on_card?
card_comments.many? && card_comments_prior.where(creator_id: creator_id).none?
end
def follows_comment_by_another_author?
card_comments.many? && card_comments_prior.last&.creator != creator
end
after_create_commit :watch_card_by_creator, :track_commented_card
after_destroy_commit :cleanup_events
def to_partial_path
"cards/#{super}"
end
private
def cleanup_events
# Delete events that reference through event_summary
if message&.event_summary.present?
Event.where(summary: message.event_summary).destroy_all
end
def watch_card_by_creator
card.watch_by creator
end
def track_commented_card
card.track_event :commented, comment_id: id
end
# FIXME: This isn't right. We need to introduce an eventable polymorphic association for this.
def cleanup_events
# Delete events that reference directly in particulars
Event.where(particulars: { comment_id: id }).destroy_all
end
def card_comments_prior
card_comments.where(created_at: ...created_at)
end
def card_comments
Comment.joins(:message).where(messages: { card: card })
end
end
-1
View File
@@ -2,5 +2,4 @@ class Current < ActiveSupport::CurrentAttributes
attribute :session
delegate :user, to: :session, allow_nil: true
delegate :account, to: :user, allow_nil: true
end
-8
View File
@@ -5,21 +5,13 @@ class Event < ApplicationRecord
belongs_to :summary, touch: true, class_name: "EventSummary"
belongs_to :card
has_one :account, through: :creator
has_one :message, through: :summary
has_one :comment, through: :message, source: :messageable, source_type: "Comment"
scope :chronologically, -> { order created_at: :asc, id: :desc }
scope :non_boosts, -> { where.not action: :boosted }
scope :boosts, -> { where action: :boosted }
scope :comments, -> { where action: :commented }
after_create -> { card.touch_last_active_at }
def boosted?
action == "boosted"
end
def commented?
action == "commented"
end
+2 -2
View File
@@ -6,10 +6,10 @@ module Event::Particulars
end
def assignees
@assignees ||= account.users.where id: assignee_ids
@assignees ||= User.where id: assignee_ids
end
def comment
@comment ||= account.comments.find_by(id: comment_id)
@comment ||= Comment.find_by id: comment_id
end
end
+1 -13
View File
@@ -5,16 +5,12 @@ class EventSummary < ApplicationRecord
# FIXME: Consider persisting the body and compute at write time.
def body
"#{main_summary} #{boosts_summary}".squish
events.map { |event| summarize(event) }.join(" ")
end
private
delegate :time_ago_in_words, to: "ApplicationController.helpers"
def main_summary
events.non_boosts.map { |event| summarize(event) }.join(" ")
end
def summarize(event)
case event.action
when "published"
@@ -39,12 +35,4 @@ class EventSummary < ApplicationRecord
"#{event.creator.name} changed title from '#{event.particulars.dig('particulars', 'old_title')}' to '#{event.particulars.dig('particulars', 'new_title')}'."
end
end
def boosts_summary
if tally = events.boosts.group(:creator).count.presence
tally.map do |creator, count|
"#{creator.name} +#{count}"
end.to_sentence + "."
end
end
end
+1 -1
View File
@@ -1,7 +1,7 @@
module Filter::Fields
extend ActiveSupport::Concern
INDEXES = %w[ most_discussed most_boosted newest oldest latest stalled ]
INDEXES = %w[ newest oldest latest stalled ]
delegate :default_value?, to: :class
+3 -2
View File
@@ -1,6 +1,7 @@
class FirstRun
def self.create!(user_attributes)
account = Account.create!(name: "Fizzy")
account.users.member.create!(user_attributes)
Account.create!(name: "Fizzy")
Closure::Reason.create_defaults
User.member.create!(user_attributes)
end
end
-12
View File
@@ -4,16 +4,4 @@ class Message < ApplicationRecord
delegated_type :messageable, types: Messageable::TYPES, inverse_of: :message, dependent: :destroy
scope :chronologically, -> { order created_at: :asc, id: :desc }
after_create :created
after_destroy :destroyed
private
def created
card.comment_created(comment) if comment?
end
def destroyed
card.comment_destroyed if comment?
end
end
-2
View File
@@ -4,8 +4,6 @@ class Reaction < ApplicationRecord
scope :ordered, -> { order(:created_at) }
validates_presence_of :content
def all_emoji?
content.match? /\A(\p{Emoji_Presentation}|\p{Extended_Pictographic}|\uFE0F)+\z/u
end
-2
View File
@@ -1,8 +1,6 @@
class Tag < ApplicationRecord
include Filterable
belongs_to :account, default: -> { Current.account }, touch: true
has_many :taggings, dependent: :destroy
has_many :cards, through: :taggings
-2
View File
@@ -1,8 +1,6 @@
class User < ApplicationRecord
include Accessor, Assignee, Avatar, Role, Transferable
belongs_to :account
has_many :sessions, dependent: :destroy
has_secure_password validations: false
+1 -1
View File
@@ -11,6 +11,6 @@ module User::Accessor
private
def grant_access_to_collections
Access.insert_all account.collections.all_access.pluck(:id).collect { |collection_id| { collection_id: collection_id, user_id: id } }
Access.insert_all Collection.all_access.pluck(:id).collect { |collection_id| { collection_id: collection_id, user_id: id } }
end
end
+2 -4
View File
@@ -10,10 +10,8 @@ module User::Role
end
class_methods do
def find_or_create_system_user(account)
account.users.find_or_create_by!(role: :system) do |user|
user.name = "System"
end
def system
find_or_create_by!(role: :system) { it.name = "System" }
end
end
-1
View File
@@ -1,4 +1,3 @@
class Workflow < ApplicationRecord
belongs_to :account
has_many :stages, dependent: :delete_all
end
+1 -1
View File
@@ -11,7 +11,7 @@
<%= icon_tag "caret-down" %>
</summary>
<div class="expander__content btn borderless">
<% Current.account.closure_reasons.labels.each do |label| %>
<% Closure::Reason.labels.each do |label| %>
<%= button_to card_closure_path(card, reason: label), class: "expander__item btn" do %>
<span class="overflow-ellipsis"><%= label %></span>
<% end %>
+2 -2
View File
@@ -1,6 +1,6 @@
<%= messages_tag(card) do %>
<%= render partial: "messages/message", collection: card.messages, cached: true %>
<%= render "cards/comments/new", card: card, cached: true %>
<%= render partial: "cards/messages/message", collection: card.messages, cached: true %>
<%= render "cards/comments/new", card: card %>
<div class="comments__subscribers flex flex-column margin-block txt-align-start full-width">
<strong class="txt-uppercase">Subscribers</strong>
-10
View File
@@ -1,10 +0,0 @@
<%= form_with url: card_boosts_path(card), class: "pad-inline flex align-center gap" do %>
<span class="overflow-ellipsis">
<strong><%= number_field_tag :boost_count, card.boosts_count, min: 1, class: "boost__input input", autocomplete: "off" %></strong>
<span><%= card.boosts_count == 1 ? "boost" : "boosts" %></span>
</span>
<%= tag.button class: "btn", type: :submit, style: "font-size: 0.4em" do %>
<%= icon_tag "add" %>
<span class="for-screen-reader">Boost</span>
<% end %>
<% end %>
@@ -1,7 +0,0 @@
<%= turbo_stream.update dom_id(@card, :boosts) do %>
<%= render "cards/boosts/boosts", card: @card %>
<% end %>
<%= turbo_stream.replace dom_id(@card, :messages) do %>
<%= render "cards/messages", card: @card %>
<% end %>
-26
View File
@@ -1,26 +0,0 @@
<% cache comment do %>
<%= turbo_frame_tag dom_id(comment) do %>
<div class="comment__content flex flex-column flex-item-grow full-width">
<div class="comment__author flex align-center gap-half">
<strong>
<%= link_to comment.creator.name, user_path(comment.creator), class: "txt-ink btn btn--plain fill-transparent", data: { turbo_frame: "_top" } %>
</strong>
<%= link_to card_path(comment.card, anchor: "comment_#{comment.id}"), class: "txt-undecorated txt-uppercase" do %>
<%= local_datetime_tag comment.created_at, style: :shortdate, class: "txt-ink translucent" %>
<% end %>
<%= link_to edit_card_comment_path(comment.card, comment),
class: "comment__edit btn btn--plain txt-xx-small fill-transparent translucent flex-item-justify-end" do %>
<%= icon_tag "menu-dots-horizontal" %>
<span class="for-screen-reader">Edit this comment</span>
<% end %>
</div>
<div class="comment__body txt-align-start">
<%= sanitize comment.body_html %>
</div>
<%= render "cards/comments/reactions/reactions", comment: comment %>
</div>
<% end %>
<% end %>
+30 -5
View File
@@ -1,7 +1,32 @@
<%= comment_tag comment do %>
<figure class="comment__avatar flex-item-no-shrink" aria-hidden="true">
<%= avatar_tag comment.creator, hidden_for_screen_reader: true %>
</figure>
<% cache comment do %>
<%= turbo_frame_tag comment do %>
<div class="comment flex align-start full-width" data-creator-id="<%= comment.creator_id %>" data-created-by-current-user-target="creation">
<figure class="comment__avatar flex-item-no-shrink" aria-hidden="true">
<%= avatar_tag comment.creator, hidden_for_screen_reader: true %>
</figure>
<%= render "cards/comments/body", comment: comment %>
<div class="comment__content flex flex-column flex-item-grow full-width">
<div class="comment__author flex align-center gap-half">
<strong>
<%= link_to comment.creator.name, comment.creator, class: "txt-ink btn btn--plain fill-transparent", data: { turbo_frame: "_top" } %>
</strong>
<%= link_to card_path(comment.card, anchor: "comment_#{comment.id}"), class: "txt-undecorated txt-uppercase" do %>
<%= local_datetime_tag comment.created_at, style: :shortdate, class: "txt-ink translucent" %>
<% end %>
<%= link_to edit_card_comment_path(comment.card, comment),
class: "comment__edit btn btn--plain txt-xx-small fill-transparent translucent flex-item-justify-end" do %>
<%= icon_tag "menu-dots-horizontal" %> <span class="for-screen-reader">Edit this comment</span>
<% end %>
</div>
<div class="comment__body txt-align-start">
<%= sanitize comment.body_html %>
</div>
<%= render "cards/comments/reactions/reactions", comment: comment %>
</div>
</div>
<% end %>
<% end %>
@@ -1,25 +1,24 @@
<% cache reaction do %>
<div id="<%= dom_id(reaction) %>"
class="reaction flex-inline postion--relative max-width align-center fill-white gap"
data-controller="reaction-delete"
data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>"
data-controller="reaction-delete" data-reaction-delete-perform-class="reaction--deleting"
data-reaction-delete-reveal-class="expanded" data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>">
<figure class="reaction__avatar margin-none flex-item-no-shrink">
<%= avatar_tag reaction.reacter, aria: { label: "#{reaction.reacter.name} reacted #{reaction.content}" } %>
</figure>
<div id="<%= dom_id(reaction) %>"
class="reaction flex-inline postion--relative max-width align-center fill-white gap"
data-controller="reaction-delete"
data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>"
data-controller="reaction-delete" data-reaction-delete-perform-class="reaction--deleting"
data-reaction-delete-reveal-class="expanded" data-reaction-delete-reacter-id-value="<%= reaction.reacter.id %>">
<figure class="reaction__avatar margin-none flex-item-no-shrink">
<%= avatar_tag reaction.reacter, aria: { label: "#{reaction.reacter.name} reacted #{reaction.content}" } %>
</figure>
<%= tag.span reaction.content, role: "button",
class: [ "txt-small", { "txt-medium": reaction.all_emoji? } ],
data: { action: "click->reaction-delete#reveal keydown.enter->reaction-delete#reveal:prevent", reaction_delete_target: "content" } %>
<%= tag.span reaction.content, role: "button",
class: [ "txt-small", { "txt-medium": reaction.all_emoji? } ],
data: { action: "click->reaction-delete#reveal keydown.enter->reaction-delete#reveal:prevent", reaction_delete_target: "content" } %>
<%= button_to card_comment_reaction_path(comment.card, comment, reaction),
method: :delete,
class: "btn btn--negative flex-item-justify-end reaction__delete",
data: { action: "reaction-delete#perform", reaction_delete_target: "button" } do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Delete this reaction</span>
<% end %>
</div>
<span id="delete_reaction_accessible_label" class="for-screen-reader">Press enter to delete this reaction</span>
<% end %>
<%= button_to card_comment_reaction_path(reaction.comment.card, reaction.comment, reaction),
method: :delete,
class: "btn btn--negative flex-item-justify-end reaction__delete",
data: { action: "reaction-delete#perform", reaction_delete_target: "button" } do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Delete this reaction</span>
<% end %>
</div>
<span id="delete_reaction_accessible_label" class="for-screen-reader">Press enter to delete this reaction</span>
@@ -1,16 +1,14 @@
<%= turbo_frame_tag comment, :reacting do %>
<%= turbo_stream_from comment, :comments %>
<div class="reactions flex flex-wrap align-center gap full-width">
<div class="flex-inline flex-wrap gap" id="<%= dom_id(comment, :reactions) %>" data-turbo-streaming-target="container">
<%= render partial: "cards/comments/reactions/reaction", collection: comment.reactions.ordered, locals: { comment: comment }, cached: true %>
<div id="<%= dom_id(comment, :reactions) %>" class="flex-inline flex-wrap gap">
<%= render partial: "cards/comments/reactions/reaction", collection: comment.reactions.includes(:reacter).ordered %>
</div>
<%= turbo_frame_tag comment, :new_reaction do %>
<div class="flex-inline" data-controller="soft-keyboard">
<%= link_to new_card_comment_reaction_path(comment.card, comment), role: "button",
class: "btn reaction__action", action: "soft-keyboard#open" do %>
<%= icon_tag "reaction" %>
<span class="for-screen-reader">Add a reaction</span>
<%= icon_tag "reaction" %> <span class="for-screen-reader">Add a reaction</span>
<% end %>
</div>
<% end %>
@@ -1,5 +1,5 @@
<%= turbo_frame_tag dom_id(@comment, :new_reaction) do %>
<%= form_with url: card_comment_reactions_path(@comment.card, @comment),
<%= turbo_frame_tag @comment, :new_reaction do %>
<%= form_with model: [ @comment.card, @comment, Reaction.new ],
class: "reaction reaction__form flex-inline postion--relative max-width align-center fill-white gap expanded",
html: { aria: { label: "New reaction" } },
data: { controller: "form", turbo_frame: dom_id(@comment, :reacting), action: "keydown.esc->form#cancel" } do |form| %>
@@ -7,20 +7,18 @@
<figure class="reaction__avatar margin-none flex-item-no-shrink">
<%= avatar_tag Current.user %>
</figure>
<%= form.text_field :content, name: "reaction[content]", autofocus: true, autocomplete: "off", autocorrect: "off", maxlength: 16,
required: true, pattern: /\S+.*/, class: "input reaction__input txt-small",
aria: { label: "Add a reaction" } %>
<%= form.text_field :content, autofocus: true, autocomplete: "off", autocorrect: "off", maxlength: 16,
required: true, pattern: /\S+.*/, class: "input reaction__input txt-small", aria: { label: "Add a reaction" } %>
</label>
<%= form.button class: "btn btn--reversed", type: "submit" do %>
<%= icon_tag "check" %>
<span class="for-screen-reader">Submit</span>
<%= icon_tag "check" %> <span class="for-screen-reader">Submit</span>
<% end %>
<%= link_to card_comment_reactions_path(@comment.card, @comment), role: "button",
data: { turbo_frame: dom_id(@comment, :reactions), form_target: "cancel" }, class: "btn btn--negative" do %>
<%= icon_tag "minus" %>
<span class="for-screen-reader">Cancel</span>
data: { turbo_frame: dom_id(@comment, :reacting), form_target: "cancel" }, class: "btn btn--negative" do %>
<%= icon_tag "minus" %> <span class="for-screen-reader">Cancel</span>
<% end %>
<% end %>
<% end %>
+1 -1
View File
@@ -1 +1 @@
<%= render "cards/comments/body", comment: @comment %>
<%= render "cards/comments/comment", comment: @comment %>
+1 -1
View File
@@ -1 +1 @@
<%= render "cards/comments/body", comment: @comment %>
<%= render "cards/comments/comment", comment: @comment %>
+1 -1
View File
@@ -11,7 +11,7 @@
<h1 class="card__title">
<% if card.published? %>
<%= turbo_frame_tag card, :edit do %>
<%= link_to card_title(card), edit_collection_card_path(card.collection, card), class: "card__title-link overflow-line-clamp" %>
<%= link_to card.title, edit_collection_card_path(card.collection, card), class: "card__title-link overflow-line-clamp" %>
<% end %>
<% else %>
<%= form_with model: card, url: collection_card_path(card.collection, card), id: "card_form", data: { controller: "auto-save" } do |form| %>
+2 -2
View File
@@ -7,11 +7,11 @@
<div class="card__body flex gap full-width flex-item-grow">
<h1 class="card__title">
<span class="card__title-link overflow-line-clamp"><%= card_title(card) %></span>
<span class="card__title-link overflow-line-clamp"><%= card.title %></span>
</h1>
<%= link_to collection_card_path(card.collection, card), class: "card__link" do %>
<span class="for-screen-reader"><%= card_title(card) %></span>
<span class="for-screen-reader"><%= card.title %></span>
<% end %>
<%= render "cards/stagings/stages", card: card if card.doing? %>
+4 -4
View File
@@ -2,10 +2,10 @@
<%= form_with model: @card, url: collection_card_path(@card.collection, @card), class: "flex flex-column gap full-width", data: { controller: "form" } do |form| %>
<%= form.label :title, class: "flex flex-column align-center" do %>
<%= form.text_area :title, class: "input input--textarea full-width borderless txt-align-start card__title-field",
required: true, autofocus: true, placeholder: "Name it…",
rows: 3,
data: { action: "keydown.enter->form#submit:prevent keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel focus->form#select" },
style: "color: var(--spat-color); --input-border-radius: 0" %>
required: true, autofocus: true, placeholder: "Name it…",
rows: 3,
data: { action: "keydown.enter->form#submit:prevent keydown.ctrl+enter->form#submit:prevent keydown.meta+enter->form#submit:prevent keydown.esc->form#cancel focus->form#select" },
style: "color: var(--spat-color); --input-border-radius: 0" %>
<% end %>
<%= form.button "Save", type: :submit, hidden: true %>
<%= link_to "Close editor and discard changes", collection_card_path(@card.collection, @card), data: { form_target: "cancel" }, hidden: true %>
+2 -1
View File
@@ -1,4 +1,5 @@
<% @page_title = card_title(@card) %>
<% @page_title = @card.title %>
<%= turbo_stream_from @card %>
<% content_for :header do %>
<% if @card.creating? && @card.can_recover_abandoned_creation? %>
+1 -1
View File
@@ -75,7 +75,7 @@
<strong class="txt-large">Choose a workflow</strong>
<p class="margin-none">Use a workflow to track progress in this collection.</p>
<%= form_with model: @collection, url: collection_workflow_path(@collection), local: true, method: :patch, data: { controller: "form" } do |form| %>
<%= form.select :workflow_id, Current.account.workflows.map { |w| [w.name, w.id] }, { include_blank: "Choose…" }, class: "input input--select full-width", data: { action: "change->form#submit" } %>
<%= form.select :workflow_id, Workflow.all.map { |w| [w.name, w.id] }, { include_blank: "Choose…" }, class: "input input--select full-width", data: { action: "change->form#submit" } %>
<% end %>
<p><%= link_to "Manage workflows", workflows_path, class: "btn btn--plain txt-link" %></p>
</div>
+2 -2
View File
@@ -5,11 +5,11 @@
<div class="btn btn--circle btn--placeholder flex-item-justify-start"></div>
<div class="events__index-header flex-inline flex-column center gap-half margin-block-end-double txt-align-center">
<h2 class="txt-large txt-nowrap center"><%= Current.account.name %> / Latest activity</h2>
<h2 class="txt-large txt-nowrap center"><%= Account.sole.name %> / Latest activity</h2>
<%= render "events/filter" %>
</div>
<%= link_to account_users_path, class: "btn flex-item-justify-end" do %>
<%= link_to users_path, class: "btn flex-item-justify-end" do %>
<%= icon_tag "settings" %>
<span class="for-screen-reader">Account settings</span>
<% end %>
+1 -1
View File
@@ -35,7 +35,7 @@
<%= icon_tag "check", class: "checked flex-item-justify-end" %>
</div>
<% Current.account.users.active.order(:name).each do |user| %>
<% User.active.order(:name).each do |user| %>
<div class="btn popup__item">
<%= form.check_box "assignee_ids[]", {
checked: filter.assignees.include?(user),
+1 -1
View File
@@ -3,5 +3,5 @@
<%= turbo_stream_from collection %>
<% end %>
<% else %>
<%= turbo_stream_from Current.account, :collections %>
<%= turbo_stream_from :collections %>
<% end %>
+1 -1
View File
@@ -37,7 +37,7 @@
<%= icon_tag "check", class: "checked flex-item-justify-end" %>
</div>
<% Current.account.users.active.without(Current.user).order(:name).each do |user| %>
<% User.active.without(Current.user).order(:name).each do |user| %>
<div class="btn popup__item">
<%= form.check_box :creator_ids, {
multiple: true,
+4 -4
View File
@@ -78,7 +78,7 @@
<% end %>
</menu>
<% if workflow = Current.account.workflows.first %>
<% if workflow = Workflow.first %>
<menu class="filter__menu">
<li class="filter__label"><strong>In Stage</strong></li>
<% workflow.stages.each do |stage| %>
@@ -93,7 +93,7 @@
</menu>
<% end %>
<% if (tags = Current.account.tags.order(:title)).any? %>
<% if (tags = Tag.all.order(:title)).any? %>
<div class="filter__menu" role="group" aria-label="Tagged">
<div class="filter__label"><strong>Tagged</strong></div>
@@ -133,7 +133,7 @@
</label>
</div>
<% Current.account.users.active.without(Current.user).order(:name).each do |user| %>
<% User.active.without(Current.user).order(:name).each do |user| %>
<div>
<label class="btn filter__button">
<%= check_box_tag "assignee_ids[]", user.id, filter.assignees.include?(user), class: "visually-hidden",
@@ -157,7 +157,7 @@
</label>
</div>
<% Current.account.users.active.without(Current.user).order(:name).each do |user| %>
<% User.active.without(Current.user).order(:name).each do |user| %>
<div>
<label class="btn filter__button">
<%= check_box_tag "creator_ids[]", user.id, filter.creators.include?(user), class: "visually-hidden" %>
+2 -3
View File
@@ -1,4 +1,4 @@
<% if Current.account.workflows.any? %>
<% if Workflow.any? %>
<div class="quick-filter position-relative" data-controller="dialog" data-action="keydown.esc->dialog#close click@document->dialog#closeOnClickOutside">
<button class="btn input input--select flex-inline txt-small" data-action="click->dialog#open:stop">
<span class="overflow-ellipsis">
@@ -20,8 +20,7 @@
<%= filter_hidden_field_tag key, value %>
<% end %>
<% workflow = Current.account.workflows.first %>
<% workflow.stages.each do |stage| %>
<% Workflow.first.stages.each do |stage| %>
<div class="btn popup__item">
<%= form.check_box :stage_ids, {
multiple: true,
+1 -1
View File
@@ -22,7 +22,7 @@
<span class="overflow-ellipsis">Clear all</span>
<% end %>
<% Current.account.tags.order(:title).each do |tag| %>
<% Tag.order(:title).each do |tag| %>
<div class="btn popup__item">
<%= form.check_box "tag_ids[]", {
checked: filter.tags.include?(tag),
@@ -1,14 +1,14 @@
<% cache [notification, "2025-04-09"] do %>
<% cache notification do %>
<%= notification_tag notification do %>
<div class="avatar txt-x-small flex-item-no-shrink">
<%= avatar_image_tag(notification.creator) %>
<%= avatar_image_tag notification.creator %>
</div>
<div class="flex flex-column min-width flex-item-grow">
<strong class="overflow-ellipsis notification__title txt-small txt-tight-lines"><%= notification_title(notification) %></strong>
<div class="overflow-ellipsis txt-small txt-tight-lines">
<% if notification.event.action == "commented" %>
<%= "#{ strip_tags(notification.event.comment.body_html).blank? ? "#{ notification.event.creator.name } replied" : "#{ notification.event.creator.name }:" } #{ strip_tags(notification.event.comment.body_html).truncate(200) }" %>
<%= "#{strip_tags(notification.event.comment.body_html).blank? ? "#{notification.event.creator.name} replied" : "#{notification.event.creator.name}:" } #{strip_tags(notification.event.comment.body_html).truncate(200)}" %>
<% else %>
<%= notification_body(notification) %>
<% end %>
@@ -1,5 +1,5 @@
<div class="flex flex-column align-center gap">
<% url = join_url(Current.account.join_code) %>
<% url = join_url(Account.sole.join_code) %>
<label class="flex flex-column gap full-width txt-align-center">
<strong id="invite_label" class="invite-label">Share to invite more people</strong>
@@ -2,12 +2,12 @@
<%= avatar_tag user, class: "flex-item-no-shrink" %>
<strong class="overflow-ellipsis">
<%= link_to user.name, user_path(user), class: "txt-ink btn btn--plain" %>
<%= link_to user.name, user, class: "txt-ink btn btn--plain" %>
</strong>
<hr class="separator--horizontal flex-item-grow" style="--border-color: var(--color-subtle-dark); --border-style: dashed" aria-hidden="true">
<%= form_with model: user, url: account_user_path(user), data: { controller: "form" }, method: :patch do | form | %>
<%= form_with model: user, url: user_role_path(user), data: { controller: "form" }, method: :patch do | form | %>
<label class="btn txt-small flex-item-no-shrink" for="<%= dom_id(user, :role) %>" arial-label="Role: <%= user.admin? ? "Administrator" : "Member" %>">
<%= icon_tag "crown" %>
<%= form.check_box :role, { data: { action: "form#submit" }, disabled: !Current.user.can_administer?(user), hidden: true, id: dom_id(user, :role) }, "admin", "member" %>
@@ -15,7 +15,7 @@
<% end %>
<%# FIXME: Move this Current.user check to a stimulus controller that just checks for admin? or the like we so we can cache user list %>
<%= button_to account_user_path(user), method: :delete, class: "btn txt-small btn--negative flex-item-no-shrink",
<%= button_to user, method: :delete, class: "btn txt-small btn--negative flex-item-no-shrink",
disabled: !Current.user.can_administer?(user),
data: { turbo_confirm: "Are you sure you want to permanently remove this person from the account?" } do %>
<%= icon_tag "minus" %>
@@ -16,9 +16,9 @@
<% end %>
<div class="panel borderless center pad fill-shade flex flex-column margin-block">
<%= render "accounts/users/invite" %>
<%= render "users/invite" %>
</div>
<div class="panel borderless center pad fill-none flex flex-column gap">
<%= render partial: "accounts/users/user", collection: @users %>
<%= render @users %>
</div>
+1 -1
View File
@@ -2,7 +2,7 @@
<% content_for :header do %>
<nav>
<%= link_to_back fallback_path: account_users_path %>
<%= link_to_back fallback_path: users_path %>
<span class="btn btn--placeholder"></span>
+5 -5
View File
@@ -2,10 +2,11 @@ Rails.application.routes.draw do
resource :first_run
resource :account do
scope module: :accounts do
resource :join_code
resources :users
end
resource :join_code, module: :accounts
end
resources :users do
resource :role, module: :users
end
resources :collections do
@@ -35,7 +36,6 @@ Rails.application.routes.draw do
resource :goldness
resources :assignments
resources :boosts
resources :stagings
resources :taggings
@@ -0,0 +1,5 @@
class DropCardCommentsCount < ActiveRecord::Migration[8.1]
def change
remove_column :cards, :comments_count
end
end
@@ -0,0 +1,5 @@
class RemoveBoostsCountFromCards < ActiveRecord::Migration[8.1]
def change
remove_column :cards, :boosts_count
end
end
@@ -0,0 +1,9 @@
class RemoveAccountIds < ActiveRecord::Migration[8.1]
def change
remove_column :closure_reasons, :account_id
remove_column :collections, :account_id
remove_column :tags, :account_id
remove_column :users, :account_id
remove_column :workflows, :account_id
end
end
Generated
+1 -16
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
ActiveRecord::Schema[8.1].define(version: 2025_04_12_170620) do
create_table "accesses", force: :cascade do |t|
t.integer "collection_id", null: false
t.datetime "created_at", null: false
@@ -113,9 +113,7 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
t.float "activity_score", default: 0.0, null: false
t.datetime "activity_score_at"
t.float "activity_score_order", default: 0.0, null: false
t.integer "boosts_count", default: 0, null: false
t.integer "collection_id", null: false
t.integer "comments_count", default: 0, null: false
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.date "due_on"
@@ -131,11 +129,9 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
end
create_table "closure_reasons", force: :cascade do |t|
t.integer "account_id"
t.datetime "created_at", null: false
t.string "label"
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_closure_reasons_on_account_id"
end
create_table "closures", force: :cascade do |t|
@@ -150,14 +146,12 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
end
create_table "collections", force: :cascade do |t|
t.integer "account_id", null: false
t.boolean "all_access", default: false, null: false
t.datetime "created_at", null: false
t.integer "creator_id", null: false
t.string "name", null: false
t.datetime "updated_at", null: false
t.integer "workflow_id"
t.index ["account_id"], name: "index_collections_on_account_id"
t.index ["creator_id"], name: "index_collections_on_creator_id"
t.index ["workflow_id"], name: "index_collections_on_workflow_id"
end
@@ -289,16 +283,12 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
end
create_table "tags", force: :cascade do |t|
t.integer "account_id", null: false
t.datetime "created_at", null: false
t.string "title"
t.datetime "updated_at", null: false
t.index ["account_id", "title"], name: "index_tags_on_account_id_and_title", unique: true
t.index ["account_id"], name: "index_tags_on_account_id"
end
create_table "users", force: :cascade do |t|
t.integer "account_id", null: false
t.boolean "active", default: true, null: false
t.datetime "created_at", null: false
t.string "email_address"
@@ -306,7 +296,6 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
t.string "password_digest"
t.string "role", default: "member", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_users_on_account_id"
t.index ["email_address"], name: "index_users_on_email_address", unique: true
t.index ["role"], name: "index_users_on_role"
end
@@ -331,11 +320,9 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
end
create_table "workflows", force: :cascade do |t|
t.integer "account_id", null: false
t.datetime "created_at", null: false
t.string "name", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_workflows_on_account_id"
end
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
@@ -356,11 +343,9 @@ ActiveRecord::Schema[8.1].define(version: 2025_04_10_141409) do
add_foreign_key "sessions", "users"
add_foreign_key "taggings", "cards"
add_foreign_key "taggings", "tags"
add_foreign_key "users", "accounts"
add_foreign_key "watches", "cards"
add_foreign_key "watches", "users"
add_foreign_key "workflow_stages", "workflows"
add_foreign_key "workflows", "accounts"
# Virtual tables defined in this database.
# Note that virtual tables may not work with other database engines. Be careful if changing database.
+77 -217
View File
@@ -50,26 +50,6 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: involvement
cast_type: &7 !ruby/object:ActiveModel::Type::String
true: t
false: f
precision:
scale:
limit:
sql_type_metadata: &8 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: varchar
type: :string
limit:
precision:
scale:
'null': false
default: watching
default_function:
collation:
comment:
- &9 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: updated_at
@@ -90,6 +70,26 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: involvement
cast_type: &7 !ruby/object:ActiveModel::Type::String
true: t
false: f
precision:
scale:
limit:
sql_type_metadata: &8 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: varchar
type: :string
limit:
precision:
scale:
'null': false
default: watching
default_function:
collation:
comment:
accounts:
- *5
- *6
@@ -412,27 +412,7 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: boosts_count
cast_type: *1
sql_type_metadata: *2
'null': false
default: 0
default_function:
collation:
comment:
- *24
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: comments_count
cast_type: *1
sql_type_metadata: *2
'null': false
default: 0
default_function:
collation:
comment:
- *6
- &25 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -463,16 +443,6 @@ columns:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: last_active_at
cast_type: *3
sql_type_metadata: *4
'null': false
default:
default_function:
collation:
comment:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: stage_id
@@ -493,7 +463,7 @@ columns:
default_function:
collation:
comment:
- &33 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- &32 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: title
cast_type: *7
@@ -504,19 +474,18 @@ columns:
collation:
comment:
- *9
closure_reasons:
- *5
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: account_id
cast_type: *1
sql_type_metadata: *2
'null': true
name: last_active_at
cast_type: *3
sql_type_metadata: *4
'null': false
default:
default_function:
collation:
comment:
- *6
closure_reasons:
- *5
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: label
@@ -527,21 +496,12 @@ columns:
default_function:
collation:
comment:
- *6
- *9
closures:
- *5
- *21
- *6
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: reason
cast_type: *7
sql_type_metadata: *8
'null': false
default:
default_function:
collation:
comment:
- *9
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
@@ -553,26 +513,26 @@ columns:
default_function:
collation:
comment:
collections:
- *5
- &32 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: account_id
cast_type: *1
sql_type_metadata: *2
name: reason
cast_type: *7
sql_type_metadata: *8
'null': false
default:
default_function:
collation:
comment:
collections:
- *5
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: all_access
cast_type: &34 !ruby/object:ActiveModel::Type::Boolean
cast_type: &33 !ruby/object:ActiveModel::Type::Boolean
precision:
scale:
limit:
sql_type_metadata: &35 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type_metadata: &34 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata
sql_type: boolean
type: :boolean
limit:
@@ -880,18 +840,16 @@ columns:
- *9
tags:
- *5
- *32
- *6
- *33
- *32
- *9
users:
- *5
- *32
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: active
cast_type: *34
sql_type_metadata: *35
cast_type: *33
sql_type_metadata: *34
'null': false
default: true
default_function:
@@ -939,8 +897,8 @@ columns:
- !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column
auto_increment:
name: watching
cast_type: *34
sql_type_metadata: *35
cast_type: *33
sql_type_metadata: *34
'null': false
default: true
default_function:
@@ -973,7 +931,6 @@ columns:
comment:
workflows:
- *5
- *32
- *6
- *10
- *9
@@ -1408,6 +1365,22 @@ indexes:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: cards
name: index_cards_on_stage_id
unique: false
columns:
- stage_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: cards
name: index_cards_on_last_active_at_and_status
@@ -1425,56 +1398,8 @@ indexes:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: cards
name: index_cards_on_stage_id
unique: false
columns:
- stage_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
closure_reasons:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: closure_reasons
name: index_closure_reasons_on_account_id
unique: false
columns:
- account_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
closure_reasons: []
closures:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: closures
name: index_closures_on_card_id
unique: true
columns:
- card_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: closures
name: index_closures_on_card_id_and_created_at
@@ -1492,6 +1417,22 @@ indexes:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: closures
name: index_closures_on_card_id
unique: true
columns:
- card_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: closures
name: index_closures_on_user_id
@@ -1509,22 +1450,6 @@ indexes:
comment:
valid: true
collections:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: collections
name: index_collections_on_account_id
unique: false
columns:
- account_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: collections
name: index_collections_on_creator_id
@@ -2014,40 +1939,7 @@ indexes:
nulls_not_distinct:
comment:
valid: true
tags:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: tags
name: index_tags_on_account_id
unique: false
columns:
- account_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: tags
name: index_tags_on_account_id_and_title
unique: true
columns:
- account_id
- title
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
tags: []
users:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: users
@@ -2081,22 +1973,6 @@ indexes:
nulls_not_distinct:
comment:
valid: true
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: users
name: index_users_on_account_id
unique: false
columns:
- account_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
watches:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: watches
@@ -2147,21 +2023,5 @@ indexes:
nulls_not_distinct:
comment:
valid: true
workflows:
- !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition
table: workflows
name: index_workflows_on_account_id
unique: false
columns:
- account_id
lengths: {}
orders: {}
opclasses: {}
where:
type:
using:
include:
nulls_not_distinct:
comment:
valid: true
version: 20250410141409
workflows: []
version: 20250412170620
+1 -1
View File
@@ -10,7 +10,7 @@ Current.session = user.sessions.last
workflow = account.workflows.first
COLLECTIONS_COUNT.times do |collection_index|
collection = account.collections.create!(name: "Collection #{collection_index}", creator: user, workflow: workflow)
collection = Collection.create!(name: "Collection #{collection_index}", creator: user, workflow: workflow)
CARDS_PER_COLLECTION.times do |card_index|
collection.cards.create!(title: "Card #{card_index}", creator: user, status: :published)
end
+2 -2
View File
@@ -4,9 +4,9 @@ CARDS_COUNT = 200
ApplicationRecord.current_tenant = "development-tenant"
account = Account.first
user = account.users.first
user = User.first
Current.session = user.sessions.last
collection = account.collections.first
collection = Collection.first
# Doing
@@ -1,41 +0,0 @@
require "test_helper"
class Accounts::UsersControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "update" do
assert_not users(:david).admin?
put account_user_url(users(:david)), params: { user: { role: "admin" } }
assert_redirected_to account_users_path
assert users(:david).reload.admin?
end
test "can't promote to special roles" do
assert_no_changes -> { users(:david).reload.role } do
put account_user_url(users(:david)), params: { user: { role: "system" } }
end
end
test "destroy" do
assert_difference -> { User.active.count }, -1 do
delete account_user_url(users(:david))
end
assert_redirected_to account_users_path
assert_nil User.active.find_by(id: users(:david).id)
end
test "non-admins cannot perform actions" do
sign_in_as :jz
put account_user_url(users(:david)), params: { user: { role: "admin" } }
assert_response :forbidden
delete account_user_url(users(:david))
assert_response :forbidden
end
end
@@ -6,18 +6,18 @@ class Cards::AssignmentsControllerTest < ActionDispatch::IntegrationTest
end
test "new" do
get new_card_assignment_url(cards(:logo))
get new_card_assignment_path(cards(:logo))
assert_response :success
end
test "create" do
assert_changes "cards(:logo).assigned_to?(users(:david))", from: false, to: true do
post card_assignments_url(cards(:logo)), params: { assignee_id: users(:david).id }, as: :turbo_stream
post card_assignments_path(cards(:logo)), params: { assignee_id: users(:david).id }, as: :turbo_stream
end
assert_response :success
assert_changes "cards(:logo).assigned_to?(users(:david))", from: true, to: false do
post card_assignments_url(cards(:logo)), params: { assignee_id: users(:david).id }, as: :turbo_stream
post card_assignments_path(cards(:logo)), params: { assignee_id: users(:david).id }, as: :turbo_stream
end
assert_response :success
end
@@ -1,27 +0,0 @@
require "test_helper"
class Cards::BoostsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "create" do
boost_count = cards(:logo).boosts_count
assert_difference "cards(:logo).reload.boosts_count", +1 do
post card_boosts_path(cards(:logo), params: { boost_count: boost_count }, format: :turbo_stream)
end
assert_turbo_stream action: :update, target: dom_id(cards(:logo), :boosts)
end
test "create with value" do
boost_count = 10
assert_changes "cards(:logo).reload.boosts_count", to: boost_count do
post card_boosts_path(cards(:logo), params: { boost_count: boost_count }, format: :turbo_stream)
end
assert_turbo_stream action: :update, target: dom_id(cards(:logo), :boosts)
end
end

Some files were not shown because too many files have changed in this diff Show More