Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Organization index and show #5187

Merged
merged 2 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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