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

[#58345] Primerize Relations tab #16989

Draft
wants to merge 3 commits into
base: dev
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions app/components/_index.sass
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@
@import "projects/row_component"
@import "op_primer/border_box_table_component"
@import "work_packages/exports/modal_dialog_component"
@import "work_package_relations_tab/index_component"
66 changes: 66 additions & 0 deletions app/components/work_package_relations_tab/index_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<%= component_wrapper(tag: "turbo-frame") do %>
<%=
flex_layout(justify_content: :space_between, align_items: :center, mb: 4) do |action_bar|
action_bar.with_column do
render(Primer::Beta::Text.new(color: :muted)) do
t("work_package_relations_tab.index.action_bar_title")
end
end
action_bar.with_column do
render(Primer::Alpha::ActionMenu.new(menu_id: "relations-list-action-menu")) do |menu|
menu.with_show_button do |button|
button.with_leading_visual_icon(icon: :"plus")
button.with_trailing_action_icon(icon: :"triangle-down")
t(:label_relation)
end
Relation::TYPES.values.each do |type_configuration_hash|
menu.with_item(label: t(type_configuration_hash[:name]).capitalize) do |item|
item.with_description.with_content("blugh")
end
end
end
end
end
%>
<%=
if relations.any?
flex_layout(classes: "work-package-relations-tab--list-container") do |flex|
relations.group_by(&:relation_type).each do |relation_type, relations_of_type|
relation_relative_to_work_package = relation_type
key_namespace = "work_package_relations_tab.relations"
base_key = "#{key_namespace}.label_#{relation_type}"

flex.with_row do
render(border_box_container(padding: :condensed)) do |border_box|
border_box.with_header(py: 3) do
flex_layout(align_items: :center) do |flex|
flex.with_column(mr: 2) do
render(Primer::Beta::Text.new(font_size: :normal, font_weight: :bold)) do
t("#{base_key}_plural").capitalize
end
end
flex.with_column do
render(Primer::Beta::Counter.new(count: relations_of_type.size,
round: true,
scheme: :primary))
end
end
end
relations_of_type.each do |relation|
border_box.with_row(style: "padding-top: 12px; padding-bottom: 12px;") do
render(WorkPackageRelationsTab::RelationComponent.new(work_package: relation.to))
end
end
end
end
end
end
else
render Primer::Beta::Blankslate.new(border: true) do |component|
component.with_visual_icon(icon: "package-dependents")
component.with_heading(tag: :h2).with_content(t("work_package_relations_tab.index.blankslate_heading"))
component.with_description { t("work_package_relations_tab.index.blankslate_description") }
end
end
%>
<% end %>
22 changes: 22 additions & 0 deletions app/components/work_package_relations_tab/index_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class WorkPackageRelationsTab::IndexComponent < ApplicationComponent
FRAME_ID = "work-package-relations-tab-content"
include ApplicationHelper
include OpPrimer::ComponentHelpers
include Turbo::FramesHelper
include OpTurbo::Streamable

attr_reader :work_package, :relations

def initialize(work_package:, relations:)
super()

@work_package = work_package
@relations = relations
end

def self.wrapper_key
FRAME_ID
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.work-package-relations-tab
&--list-container
gap: 1.5rem

// We reference an ID as one is required to be specified for the action menu list.
// It can't be nested inside the BEM model as it's placed as a #top-layer element.
#relations-list-action-menu-list
max-height: 300px
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<%= flex_layout(direction: :column, style: "gap: 0.25rem;") do |flex|
flex.with_row(flex_layout: true,
justify_content: :flex_start,
align_items: :center,
style: "gap: 0.5rem;") do |meta_information_row|
meta_information_row.with_column do
render(Primer::Beta::Text.new(font_size: :small,
font_weight: :bold,
color: :muted)) { "#{work_package.type.name.upcase}" }
end

meta_information_row.with_column do
render(Primer::Beta::Text.new(font_size: :small,
color: :muted)) { "##{work_package.id}" }
end
meta_information_row.with_column do
render(Primer::Beta::Text.new(classes: "__hl_inline_status_#{work_package.status.id}")) do
work_package.status.name
end
end
end

flex.with_row(flex_layout: true, justify_content: :space_between, align_items: :center) do |subject_line_row|
subject_line_row.with_column do
render(Primer::Beta::Link.new(href: work_package_path(work_package),
color: :default,
underline: false,
font_size: :normal,
font_weight: :bold,
target: "_blank")) { work_package.subject }
end

subject_line_row.with_column do
render(Primer::Alpha::ActionMenu.new) do |menu|
menu.with_show_button(icon: "kebab-horizontal",
'aria-label': I18n.t(:label_agenda_item_actions),
scheme: :invisible,
ml: 2,
test_selector: 'op-meeting-agenda-actions')
menu.with_item(label: "Edit relation",
href: "#",
content_arguments: {
data: { "turbo-stream": true }
}) do |item|
item.with_leading_visual_icon(icon: :pencil)
end
menu.with_item(label: "Delete relation",
scheme: :danger,
href: "#",
form_arguments: {
method: :delete, data: { confirm: t("text_are_you_sure"), "turbo-stream": true }
}) do |item|
item.with_leading_visual_icon(icon: :trash)
end
end
end
end

if work_package.description.present?
flex.with_row(flex_layout: true, justify_content: :flex_start) do |description_row|
description_row.with_column do
render(Primer::Beta::Text.new(font_size: :small,
color: :muted)) do
format_text(work_package, :description)
end
end
end
end
end %>
12 changes: 12 additions & 0 deletions app/components/work_package_relations_tab/relation_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class WorkPackageRelationsTab::RelationComponent < ApplicationComponent
include ApplicationHelper
include OpPrimer::ComponentHelpers

attr_reader :work_package

def initialize(work_package:)
super()

@work_package = work_package
end
end
159 changes: 159 additions & 0 deletions app/controllers/work_package_relations_tab_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++

class WorkPackageRelationsTabController < ApplicationController
include OpTurbo::ComponentStream
# include OpTurbo::DialogStreamHelper
# include Meetings::WorkPackageMeetingsTabComponentStreams

before_action :set_work_package
before_action :authorize_global

def index
@relations = @work_package
.relations
.includes(:to, :from)

respond_to do |format|
format.html do
render(
WorkPackageRelationsTab::IndexComponent.new(
work_package: @work_package,
relations: @relations
),
layout: false
)
end
format.turbo_stream do
replace_via_turbo_stream(component: WorkPackageRelationsTab::IndexComponent.new(
work_package: @work_package,
relations: @relations
))
render turbo_stream: turbo_streams
end
end
end

# def count
# count = Meeting.visible.where(id: @work_package.meetings.select(:id)).count
# render json: { count: }
# end

# def add_work_package_to_meeting_dialog
# respond_with_dialog WorkPackageMeetingsTab::AddWorkPackageToMeetingDialogComponent.new(work_package: @work_package)
# end

# def add_work_package_to_meeting
# call = ::MeetingAgendaItems::CreateService
# .new(user: current_user)
# .call(
# add_work_package_to_meeting_params.merge(
# work_package_id: @work_package.id,
# presenter_id: current_user.id,
# item_type: MeetingAgendaItem::ITEM_TYPES[:work_package]
# )
# )

# meeting_agenda_item = call.result

# if call.success?
# set_agenda_items(:upcoming) # always switch back to the upcoming tab after adding the work package to a meeting

# # update the whole index component as we need to update the counters in the tabbed nav as well
# update_index_component_via_turbo_stream(
# direction: :upcoming,
# agenda_items_grouped_by_meeting: @agenda_items_grouped_by_meeting,
# upcoming_meetings_count: @upcoming_meetings_count,
# past_meetings_count: @past_meetings_count
# )

# replace_tab_counter_via_turbo_stream(work_package: @work_package)

# # TODO: show success message?
# else
# # show errors in form
# update_add_to_meeting_form_component_via_turbo_stream(meeting_agenda_item:, base_errors: call.errors[:base])
# end

# respond_with_turbo_streams
# end

private

def set_work_package
@work_package = WorkPackage.find(params[:work_package_id])
@project = @work_package.project # required for authorization via before_action
rescue ActiveRecord::RecordNotFound
render_404
end

# def add_work_package_to_meeting_params
# params.require(:meeting_agenda_item).permit(:meeting_id, :notes)
# end

# def set_agenda_items(direction)
# upcoming_agenda_items_grouped_by_meeting = get_grouped_agenda_items(:upcoming)
# past_agenda_items_grouped_by_meeting = get_grouped_agenda_items(:past)

# @upcoming_meetings_count = upcoming_agenda_items_grouped_by_meeting.count
# @past_meetings_count = past_agenda_items_grouped_by_meeting.count

# @agenda_items_grouped_by_meeting = case direction
# when :upcoming
# upcoming_agenda_items_grouped_by_meeting
# when :past
# past_agenda_items_grouped_by_meeting
# end
# end

# def get_grouped_agenda_items(direction)
# get_agenda_items_of_work_package(direction).group_by(&:meeting)
# end

# def get_agenda_items_of_work_package(direction)
# agenda_items = MeetingAgendaItem
# .includes(:meeting)
# .where(meeting_id: Meeting.visible(current_user))
# .where(work_package_id: @work_package.id)
# .reorder(sort_clause(direction))

# comparison = direction == :past ? "<" : ">="
# agenda_items.where("meetings.start_time + (interval '1 hour' * meetings.duration) #{comparison} ?", Time.zone.now)
# end

# def sort_clause(direction)
# case direction
# when :upcoming
# "meetings.start_time ASC"
# when :past
# "meetings.start_time DESC"
# else
# raise ArgumentError, "Invalid direction: #{direction}. Must be one of :upcoming or :past."
# end
# end
end
22 changes: 11 additions & 11 deletions app/models/relation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ class Relation < ApplicationRecord
TYPE_RELATES => {
name: :label_relates_to, sym_name: :label_relates_to, order: 1, sym: TYPE_RELATES
},
TYPE_DUPLICATES => {
name: :label_duplicates, sym_name: :label_duplicated_by, order: 2, sym: TYPE_DUPLICATED
TYPE_PRECEDES => {
name: :label_precedes, sym_name: :label_follows, order: 6,
sym: TYPE_FOLLOWS, reverse: TYPE_FOLLOWS
},
TYPE_DUPLICATED => {
name: :label_duplicated_by, sym_name: :label_duplicates, order: 3,
sym: TYPE_DUPLICATES, reverse: TYPE_DUPLICATES
TYPE_FOLLOWS => {
name: :label_follows, sym_name: :label_precedes, order: 7,
sym: TYPE_PRECEDES
},
TYPE_BLOCKS => {
name: :label_blocks, sym_name: :label_blocked_by, order: 4, sym: TYPE_BLOCKED
Expand All @@ -65,13 +66,12 @@ class Relation < ApplicationRecord
name: :label_blocked_by, sym_name: :label_blocks, order: 5,
sym: TYPE_BLOCKS, reverse: TYPE_BLOCKS
},
TYPE_PRECEDES => {
name: :label_precedes, sym_name: :label_follows, order: 6,
sym: TYPE_FOLLOWS, reverse: TYPE_FOLLOWS
TYPE_DUPLICATES => {
name: :label_duplicates, sym_name: :label_duplicated_by, order: 6, sym: TYPE_DUPLICATED
},
TYPE_FOLLOWS => {
name: :label_follows, sym_name: :label_precedes, order: 7,
sym: TYPE_PRECEDES
TYPE_DUPLICATED => {
name: :label_duplicated_by, sym_name: :label_duplicates, order: 7,
sym: TYPE_DUPLICATES, reverse: TYPE_DUPLICATES
},
TYPE_INCLUDES => {
name: :label_includes, sym_name: :label_part_of, order: 8,
Expand Down
6 changes: 6 additions & 0 deletions app/views/work_package_relations_tab/_index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= render(
WorkPackageRelationsTab::IndexComponent.new(
work_package: @work_package,
relations: @relations
)
) %>
Loading
Loading