Skip to content

Commit

Permalink
Organizations CRUD actions + gems views
Browse files Browse the repository at this point in the history
  • Loading branch information
martinemde committed Nov 21, 2024
1 parent 323bbc5 commit 91fbb8a
Show file tree
Hide file tree
Showing 37 changed files with 876 additions and 80 deletions.
2 changes: 1 addition & 1 deletion app/assets/images/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions app/controllers/organizations/gems_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class Organizations::GemsController < ApplicationController
before_action :redirect_to_signin, unless: :signed_in?
before_action :redirect_to_new_mfa, if: :mfa_required_not_yet_enabled?
before_action :redirect_to_settings_strong_mfa_required, if: :mfa_required_weak_level_enabled?

before_action :find_organization, only: %i[index]

layout "subject"

# GET /organizations/organization_id/gems

def index
@gems = @organization.rubygems.with_versions.by_downloads.preload(:most_recent_version, :gem_download).load_async
@gems_count = @organization.rubygems.with_versions.count
end

private

def find_organization
@organization = Organization.find_by_handle!(params[:organization_id])
end
end
47 changes: 46 additions & 1 deletion app/controllers/organizations_controller.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
class OrganizationsController < ApplicationController
before_action :redirect_to_signin, only: :index, unless: :signed_in?
before_action :redirect_to_new_mfa, only: :index, if: :mfa_required_not_yet_enabled?
before_action :redirect_to_settings_strong_mfa_required, only: :index, if: :mfa_required_weak_level_enabled?

before_action :find_organization, only: %i[show edit update]

layout "subject"

# GET /organizations
def index
@memberships = current_user.memberships.includes(:organization)
end

# GET /organizations/1
def show
render plain: flash[:notice] # HACK: for tests until this view is ready
@latest_events = [] # @organization.latest_events
@gems = @organization
.rubygems
.with_versions
.by_downloads
.preload(:most_recent_version, :gem_download)
.load_async
@gems_count = @organization.rubygems.with_versions.count
@memberships = @organization.memberships
@memberships_count = @organization.memberships.count
end

def edit
add_breadcrumb t("breadcrumbs.org_name", name: @organization.handle), organization_path(@organization)
add_breadcrumb t("breadcrumbs.settings")

authorize @organization
end

def update
authorize @organization

if @organization.update(organization_params)
redirect_to organization_path(@organization)
else
render :edit
end
end

private

def find_organization
@organization = Organization.find_by_handle!(params.permit(:id).require(:id))
end

def organization_params
params.permit(organization: [:name]).require(:organization)
end
end
12 changes: 12 additions & 0 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,16 @@ class Organization < ApplicationRecord
after_create do
record_event!(Events::OrganizationEvent::CREATED, actor_gid: memberships.first&.to_gid)
end

def self.find_by_handle(handle)
find_by("lower(handle) = lower(?)", handle)
end

def self.find_by_handle!(handle)
find_by_handle(handle) || raise(ActiveRecord::RecordNotFound)
end

def to_param
handle
end
end
2 changes: 2 additions & 0 deletions app/policies/organization_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def update?
organization_member_with_role?(user, :owner) || deny(t(:forbidden))
end

alias edit? update?

def create?
true
end
Expand Down
38 changes: 35 additions & 3 deletions app/views/components/card/timeline_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

class Card::TimelineComponent < ApplicationComponent
include Phlex::Rails::Helpers::LinkTo
include Phlex::Rails::Helpers::ImageTag
include Phlex::Rails::Helpers::TimeAgoInWords

def view_template(&)
div(class: "flex grow ml-2 md:-ml-2 border-l-2 border-neutral-300") do
div(class: "flex grow ml-2 border-l-2 border-neutral-300") do
div(class: "flex flex-col grow -mt-2", &)
end
end
Expand All @@ -19,12 +20,43 @@ def timeline_item(datetime, user_link = nil, &)
# Content
div(class: "flex-1 flex-col ml-5 md:ml-7 pb-4 border-b border-neutral-300 dark:border-neutral-700") do
div(class: "flex items-center justify-between") do
span(class: "text-b3 text-neutral-600") { t("time_ago", duration: time_ago_in_words(datetime)) }
span(class: "text-b3 text-neutral-800") { user_link } if user_link
span(class: "text-b3 text-neutral-600") do
helpers.local_time_ago(datetime, class: "text-b3 text-neutral-600")
end
span(class: "text-b3 text-neutral-800 dark:text-white max-h-6") { user_link } if user_link
end

div(class: "flex flex-wrap w-full items-center justify-between", &)
end
end
end

def link_to_user(user)
link_to(profile_path(user.display_id), alt: user.display_handle, title: user.display_handle, class: "flex items-center") do
span(class: "w-6 h-6 inline-block mr-2 rounded") { helpers.avatar(48, "gravatar-#{user.id}", user) }
span { user.display_handle }
end
end

def link_to_api_key(api_key_owner)
case api_key_owner
when OIDC::TrustedPublisher::GitHubAction
div(class: "flex items-center") do
span(class: "w-6 h-6 inline-block mr-2 rounded") do
image_tag "github_icon.png", width: 48, height: 48, theme: :light, alt: "GitHub", title: api_key_owner.name
end
span { "GitHub Actions" }
end
else
raise ArgumentError, "unknown api_key_owner type #{api_key_owner.class}"
end
end

def link_to_pusher(version)
if version.pusher.present?
link_to_user(version.pusher)
elsif version.pusher_api_key&.owner.present?
link_to_api_key(version.pusher_api_key.owner)
end
end
end
20 changes: 6 additions & 14 deletions app/views/components/card_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,6 @@ def title(title, icon: nil, count: nil)
end
end

def with_list(items, &)
list do
items.each do |item|
list_item do
yield(item)
end
end
end
end

def list(**options, &)
options[:class] = "#{options[:class]} -mx-4"
ul(**options, &)
Expand All @@ -49,7 +39,9 @@ def divided_list(**options, &)

def list_item(**options, &)
options[:class] = "#{options[:class]} #{LIST_ITEM_CLASSES}"
li(**options, &)
li do
div(**options, &)
end
end

def list_item_to(url = nil, **options, &)
Expand All @@ -65,9 +57,9 @@ def list_item_to(url = nil, **options, &)
# removes padding inside the "content" area of the card so scroll bar and overflaw appear correctly
# adds a border to the top of the scrollable area to explain the content being hidden on scroll
def scrollable(**options, &)
options[:class] = "#{options[:class]} lg:max-h-96 lg:overflow-y-auto"
options[:class] << " -mx-4 -mb-6 md:-mx-10 md:-mb-10"
options[:class] << " border-t border-neutral-200 dark:border-neutral-800"
options[:class] = "#{options[:class]} lg:max-h-96 lg:overflow-y-auto " \
"-mx-4 -mb-6 md:-mx-10 md:-mb-10 " \
"border-t border-neutral-200 dark:border-neutral-800"
div(**options) do
div(class: "px-4 pt-6 md:px-10 md:pt-10", &)
end
Expand Down
66 changes: 35 additions & 31 deletions app/views/dashboards/_subject.html.erb
Original file line number Diff line number Diff line change
@@ -1,42 +1,46 @@
<%
user ||= @user || current_user
current ||= :dashboard
%>
<%# locals: (user:, current:) -%>

<div class="flex flex-wrap lg:flex-col items-start mb-6 lg:mb-10">
<%= avatar 328, "user_gravatar", theme: :dark, class: "h-24 w-24 lg:h-40 lg:w-40 rounded-lg object-cover mr-4" %>
<div class="mb-8 space-y-4">
<div class="flex flex-wrap lg:flex-col items-start mb-6 lg:mb-10">
<%= avatar 328, "user_gravatar", theme: :dark, class: "h-24 w-24 lg:h-40 lg:w-40 rounded-lg object-cover mr-4" %>

<div class="lg:w-full lg:mt-2">
<h2 class="font-bold text-h4"><%= user.display_handle %></h2>
<% if user.full_name.present? %>
<p class="text-neutral-500 text-b3"><%= user.full_name %></p>
<% end %>
<div class="lg:w-full lg:mt-2">
<h2 class="font-bold text-h4"><%= user.display_handle %></h2>
<% if user.full_name.present? %>
<p class="text-neutral-500 text-b3"><%= user.full_name %></p>
<% end %>
</div>
</div>
</div>

<% if user.public_email? || user == current_user %>
<div class="flex items-center mb-4 text-b3 lg:text-b2">
<%= icon_tag("mail", color: :primary, class: "h-6 w-6 text-orange mr-3") %>
<p class="text-neutral-800 dark:text-white"><%=
mail_to(user.email, encode: "hex")
%></p>
</div>
<% end %>
<% if user.public_email? || user == current_user %>
<div class="flex items-center mb-4 text-b3 lg:text-b2">
<%= icon_tag("mail", color: :primary, class: "h-6 w-6 text-orange mr-3") %>
<p class="text-neutral-800 dark:text-white"><%=
mail_to(user.email, encode: "hex")
%></p>
</div>
<% end %>
<% if user.twitter_username.present? %>
<div class="flex items-center mb-4 text-b3 lg:text-b2">
<%= icon_tag("x-twitter", color: :primary, class: "w-6 text-orange mr-3") %>
<p class="text-neutral-800 dark:text-white"><%=
link_to(
twitter_username(user),
twitter_url(user)
)
%></p>
</div>
<% end %>
<% if user.twitter_username.present? %>
<div class="flex items-center mb-4 text-b3 lg:text-b2">
<%= icon_tag("x-twitter", color: :primary, class: "w-6 text-orange mr-3") %>
<p class="text-neutral-800 dark:text-white"><%=
link_to(
twitter_username(user),
twitter_url(user)
)
%></p>
</div>
<% end %>
</div>

<hr class="hidden lg:block lg:mb-6 border-neutral-400 dark:border-neutral-600" />

<%= render Subject::NavComponent.new(current:) do |nav| %>
<%= nav.link t("layouts.application.header.dashboard"), dashboard_path, name: :dashboard, icon: "space-dashboard" %>
<%= nav.link t("dashboards.show.my_subscriptions"), subscriptions_path, name: :subscriptions, icon: "notifications" %>
<% if current_user.memberships.any? %>
<%= nav.link t("dashboards.show.organizations"), organizations_path, name: :organizations, icon: "organizations" %>
<% end %>
<%= nav.link t("layouts.application.header.settings"), edit_settings_path, name: :settings, icon: "settings" %>
<% end %>
20 changes: 8 additions & 12 deletions app/views/dashboards/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<% @title = t('.title') %>
<% content_for :subject do %>
<% render "dashboards/subject", user: current_user %>
<%= render "dashboards/subject", user: current_user, current: :dashboard %>
<% end %>

<!-- Main Content -->
Expand All @@ -27,14 +27,7 @@
<%= c.scrollable do %>
<%= render Card::TimelineComponent.new do |t| %>
<% @latest_updates.each do |version| %>
<%
pusher_link = if version.pusher.present?
link_to_user(version.pusher)
elsif version.pusher_api_key&.owner.present?
link_to_pusher(version.pusher_api_key.owner)
end
%>
<%= t.timeline_item(version.authored_at, pusher_link) do %>
<%= t.timeline_item(version.authored_at, t.link_to_pusher(version)) do %>
<div class="flex text-b1 text-neutral-800 dark:text-white"><%= link_to version.rubygem.name, rubygem_path(version.rubygem.slug) %></div>
<%= version_number(version) %>
<% end %>
Expand Down Expand Up @@ -101,9 +94,12 @@
<% end %>
<%= c.divided_list do %>
<% current_user.memberships.preload(:organization).each do |membership| %>
<%= c.list_item_to("#") do %>
<div class="flex justify-between">
<p class="text-neutral-800 dark:text-white"><%= membership.organization.name %></p>
<%= c.list_item_to(organization_path(membership.organization)) do %>
<div class="flex flex-row w-full justify-between items-center">
<div class="flex flex-col">
<p class="text-neutral-800 dark:text-white"><%= membership.organization.name %></p>
<p class="text-b3 text-neutral-600"><%= membership.organization.handle %></p>
</div>
<p class="text-neutral-500 capitalize"><%= membership.role %></p>
</div>
<% end %>
Expand Down
34 changes: 34 additions & 0 deletions app/views/layouts/hammy_component_preview.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="<%= I18n.locale %>">
<head>
<title><%= page_title %></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<%= stylesheet_link_tag("hammy") %>
<%= stylesheet_link_tag("tailwind", "data-turbo-track": "reload") %>
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Titillium+Web:ital,wght@0,200;0,300;0,400;0,600;0,700;0,900;1,200;1,300;1,400;1,600;1,700&display=swap" rel="stylesheet" type="text/css">
<%= yield :head %>
<%= javascript_importmap_tags %>
</head>
<body data-turbo="true" class="bg-neutral-050 dark:bg-neutral-950">
<div class="min-h-screen flex flex-col">
<!-- Content -->
<% if content_for?(:main) %>
<%= yield :main %>
<% else %>
<main class="flex-1 w-full px-8 flex-col bg-neutral-050 dark:bg-neutral-950 text-neutral-950 dark:text-neutral-050 text-b2 items-center inline-flex">
<div class="max-w-screen-xl w-full mx-auto pt-8 pb-10 mb-12 md:mb-16 lg:mb-28">
<% flash.each do |name, msg| %>
<%= render AlertComponent.new(style: name, closeable: true) do %>
<%= flash_message(name, msg) %>
<% end %>
<% end %>
<%= yield %>
</div>
</main>
<% end %>
</div>
</body>
</html>
25 changes: 25 additions & 0 deletions app/views/organizations/_subject.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<% current ||= :dashboard %>

<div class="flex flex-wrap lg:flex-col items-start mb-6 lg:mb-10">
<div class="lg:w-full lg:mt-2">
<h2 class="font-bold text-h4"><%= organization.name %></h2>
<p class="text-neutral-600 dark:text-neutral-500 text-b3"><%= organization.handle %></p>
<p class="my-1">
<span class="shrink px-3 py-1 rounded-full border border-orange text-orange items-center text-b3 uppercase font-semibold">
<%= icon_tag("organizations", size: 6, class: "-mt-1 -ml-1 mr-1 inline-block") -%><%= t("organizations.show.organization") %>
</span>
</p>
</div>
</div>

<hr class="hidden lg:block lg:mb-6 border-neutral-400 dark:border-neutral-600" />

<%= render Subject::NavComponent.new(current:) do |nav| %>
<%= nav.link t("layouts.application.header.dashboard"), organization_path(@organization), name: :dashboard, icon: "space-dashboard" %>
<%= nav.link t("organizations.show.history"), organization_path(@organization), name: :subscriptions, icon: "notifications" %>
<%= nav.link t("organizations.show.gems"), organization_gems_path(@organization), name: :gems, icon: "gems" %>
<%= nav.link t("organizations.show.members"), organization_path(@organization), name: :organizations, icon: "organizations" %>
<% if policy(@organization).edit? %>
<%= nav.link t("layouts.application.header.settings"), edit_organization_path(@organization), name: :settings, icon: "settings" %>
<% end %>
<% end %>
Loading

0 comments on commit 91fbb8a

Please sign in to comment.