Skip to content

Commit

Permalink
Feature/49951 meetings tab on work package page (#13749)
Browse files Browse the repository at this point in the history
* [#49951] Meetings tab on work package page

https://community.openproject.org/work_packages/49951

* added meetings tab for work package page as and add-on feature for #13725, specs missing

* cleanup and updated to-do comments

* cleanup left-overs of include child wps draft

* Use autocomplete select decoration for meeting selector

* Use proper prefix for turbo frame src and add content-loader example

* WIP: Adding specs for meetings tab, specs for adding agenda items through meetings tab missing

* re-enabled temporarly disabled specs

* cleanup redundant UI update

* implemented potentiallly reuseable async primer dialog, using turbo frame and a bit of stimulus

* cleanup

* finalized meeting tabs specs

* adjusted contracts towards usage in meetings tab

* Do not output meetings tab on new work packages

* Don't use through association to check for presence, use meeting itself

* Add TurboElement type to prevent any

* Rework into view

* Visit response in case frame is missing

If you're setting up the wrong frame response, you get "Content missing" by default. This is not very helpful for identifying the error.

We can listen to the turbo frame-missing event and promote it to a full visit instead

https://community.openproject.org/work_packages/50210

* Add nowrap to "Added by"

* Unify "added by" display

* Adapt create contract spec for visibility check

* Use contract to get assignable meetings

* Use explicit option to set decorated status, even if options empty

---------

Co-authored-by: Oliver Günther <[email protected]>
  • Loading branch information
jjabari-op and oliverguenther authored Sep 28, 2023
1 parent 99fdd10 commit 1a9d031
Show file tree
Hide file tree
Showing 36 changed files with 1,642 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="autocomplete-select-decoration--wrapper" hidden>
<%= content_tag :'autocomplete-select-decoration',
<%= content_tag :'opce-select-decoration',
{},
data: {
"multiselect": multiple,
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1639,6 +1639,7 @@ en:
label_add_related_work_packages: "Add related work packages"
label_add_subtask: "Add subtask"
label_added: "added"
label_added_by: "Added by %{author}"
label_added_time_by: "Added by %{author} %{age} ago"
label_additional_workflow_transitions_for_assignee: "Additional transitions allowed when the user is the assignee"
label_additional_workflow_transitions_for_author: "Additional transitions allowed when the user is the author"
Expand Down
43 changes: 16 additions & 27 deletions frontend/src/app/core/setup/global-dynamic-components.const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {
zenModeComponentSelector,
} from 'core-app/features/work-packages/components/wp-buttons/zen-mode-toggle-button/zen-mode-toggle-button.component';
import {
OpAttachmentsComponent,
attachmentsSelector,
OpAttachmentsComponent,
} from 'core-app/shared/components/attachments/attachments.component';
import {
UserAutocompleterComponent,
Expand All @@ -37,10 +37,7 @@ import {
ToastsContainerComponent,
toastsContainerSelector,
} from 'core-app/shared/components/toaster/toasts-container.component';
import {
OpSidemenuComponent,
sidemenuSelector,
} from 'core-app/shared/components/sidemenu/sidemenu.component';
import { OpSidemenuComponent, sidemenuSelector } from 'core-app/shared/components/sidemenu/sidemenu.component';
import {
CkeditorAugmentedTextareaComponent,
ckeditorAugmentedTextareaSelector,
Expand All @@ -61,10 +58,6 @@ import {
AddSectionDropdownComponent,
addSectionDropdownSelector,
} from 'core-app/shared/components/hide-section/add-section-dropdown/add-section-dropdown.component';
import {
AutocompleteSelectDecorationComponent,
autocompleteSelectDecorationSelector,
} from 'core-app/shared/components/autocompleter/autocomplete-select-decoration/autocomplete-select-decoration.component';
import {
ContentTabsComponent,
contentTabsSelector,
Expand All @@ -82,8 +75,8 @@ import {
CollapsibleSectionComponent,
} from 'core-app/shared/components/collapsible-section/collapsible-section.component';
import {
OpHeaderProjectSelectComponent,
headerProjectSelectSelector,
OpHeaderProjectSelectComponent,
} from 'core-app/shared/components/header-project-select/header-project-select.component';
import {
ProjectAutocompleterComponent,
Expand Down Expand Up @@ -137,10 +130,7 @@ import {
quickInfoMacroSelector,
WorkPackageQuickinfoMacroComponent,
} from 'core-app/shared/components/fields/macros/work-package-quickinfo-macro.component';
import {
SpotSwitchComponent,
spotSwitchSelector,
} from 'core-app/spot/components/switch/switch.component';
import { SpotSwitchComponent, spotSwitchSelector } from 'core-app/spot/components/switch/switch.component';
import { BackupComponent, backupSelector } from 'core-app/core/setup/globals/components/admin/backup.component';
import {
EnterpriseBaseComponent,
Expand All @@ -159,17 +149,14 @@ import {
enterprisePageSelector,
} from 'core-app/shared/components/enterprise-page/enterprise-page.component';
import {
OpNonWorkingDaysListComponent,
nonWorkingDaysListSelector,
OpNonWorkingDaysListComponent,
} from 'core-app/shared/components/op-non-working-days-list/op-non-working-days-list.component';
import {
EEActiveSavedTrialComponent,
enterpriseActiveSavedTrialSelector,
} from 'core-app/features/enterprise/enterprise-active-trial/ee-active-saved-trial.component';
import {
NoResultsComponent,
noResultsSelector,
} from 'app/shared/components/no-results/no-results.component';
import { NoResultsComponent, noResultsSelector } from 'app/shared/components/no-results/no-results.component';
import {
HomescreenNewFeaturesBlockComponent,
homescreenNewFeaturesBlockSelector,
Expand All @@ -191,10 +178,7 @@ import {
InAppNotificationBellComponent,
opInAppNotificationBellSelector,
} from 'core-app/features/in-app-notifications/bell/in-app-notification-bell.component';
import {
IanMenuComponent,
ianMenuSelector,
} from 'core-app/features/in-app-notifications/center/menu/menu.component';
import { IanMenuComponent, ianMenuSelector } from 'core-app/features/in-app-notifications/center/menu/menu.component';
import {
opTeamPlannerSidemenuSelector,
TeamPlannerSidemenuComponent,
Expand All @@ -215,11 +199,17 @@ import {
OpBasicSingleDatePickerComponent,
opBasicSingleDatePickerSelector,
} from 'core-app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component';
import { SpotDropModalPortalComponent, spotDropModalPortalComponentSelector } from 'core-app/spot/components/drop-modal/drop-modal-portal.component';
import { StaticAttributeHelpTextComponent, staticAttributeHelpTextSelector } from 'core-app/shared/components/attribute-help-texts/static-attribute-help-text.component';
import {
StorageLoginButtonComponent,
SpotDropModalPortalComponent,
spotDropModalPortalComponentSelector,
} from 'core-app/spot/components/drop-modal/drop-modal-portal.component';
import {
StaticAttributeHelpTextComponent,
staticAttributeHelpTextSelector,
} from 'core-app/shared/components/attribute-help-texts/static-attribute-help-text.component';
import {
opStorageLoginButtonSelector,
StorageLoginButtonComponent,
} from 'core-app/shared/components/storages/storage-login-button/storage-login-button.component';
import {
TimerAccountMenuComponent,
Expand Down Expand Up @@ -250,7 +240,6 @@ export const globalDynamicComponents:OptionalBootstrapDefinition[] = [
{ selector: hideSectionLinkSelector, cls: HideSectionLinkComponent },
{ selector: showSectionDropdownSelector, cls: ShowSectionDropdownComponent },
{ selector: addSectionDropdownSelector, cls: AddSectionDropdownComponent },
{ selector: autocompleteSelectDecorationSelector, cls: AutocompleteSelectDecorationComponent },
{ selector: contentTabsSelector, cls: ContentTabsComponent },
{ selector: globalSearchTitleSelector, cls: GlobalSearchTitleComponent },
{ selector: copyToClipboardSelector, cls: CopyToClipboardComponent },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,5 +64,6 @@ export const OPENPROJECT_AUTOCOMPLETE_COMPONENTS = [
export class OpenprojectAutocompleterModule {
constructor(injector:Injector) {
registerCustomElement('opce-autocompleter', OpAutocompleterComponent, { injector });
registerCustomElement('opce-select-decoration', AutocompleteSelectDecorationComponent, { injector });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,9 @@
// as the OpenProject form styles override them.
.Box
ul:not(.op-uc-list)
> ul:not(.op-uc-list)
margin-left: 0

segmented-control
ul
margin-left: 0

.FormControl
label
Expand All @@ -47,3 +44,6 @@ action-menu
anchored-position
ul
margin-left: 0

ul.tabnav-tabs
margin-left: 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* -- copyright
* OpenProject is an open source project management software.
* Copyright (C) 2023 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.
* ++
*/

import { Controller } from '@hotwired/stimulus';
import { TurboElement } from '../../../typings/turbo';

export default class extends Controller {
static targets = ['frameElement'];
declare readonly frameElementTarget:HTMLInputElement&TurboElement;

private initialState:string;

connect():void {
this.initialState = this.frameElementTarget.innerHTML;
}

reinitFrame():void {
this.frameElementTarget.innerHTML = this.initialState;
this.frameElementTarget.reload();
}
}
3 changes: 3 additions & 0 deletions frontend/src/typings/turbo.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface TurboElement {
reload:() => void;
}
29 changes: 20 additions & 9 deletions lib/primer/open_project/forms/autocompleter.html.erb
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
<%= render(FormControl.new(input: @input)) do %>
<%= angular_component_tag 'opce-autocompleter',
data: @autocomplete_options.delete(:data) { {} },
inputs: @autocomplete_options.merge({
classes: "ng-select--primerized #{@input.invalid? ? '-error' : ''}",
inputName: builder.field_name(@input.name),
inputValue: builder.object.send(@input.name),
defaultData: 'true'
})
%>
<% if decorated_select? %>
<%= render partial: '/augmented/autocomplete_select_decoration',
locals: {
input_name: builder.field_name(@input.name),
input_id: builder.field_id(@input.name),
select_options: select_options.map(&:to_h),
multiple: @autocomplete_options.fetch(:multiple, false),
key: @autocomplete_options.fetch(:resource, '')
} %>
<% else %>
<%= angular_component_tag 'opce-autocompleter',
data: @autocomplete_options.delete(:data) { {} },
inputs: @autocomplete_options.merge(
classes: "ng-select--primerized #{@input.invalid? ? '-error' : ''}",
inputName: builder.field_name(@input.name),
inputValue: builder.object.send(@input.name),
defaultData: 'true'
)
%>
<% end %>
<% end %>
6 changes: 5 additions & 1 deletion lib/primer/open_project/forms/autocompleter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ module Forms
class Autocompleter < Primer::Forms::BaseComponent
include AngularHelper

delegate :builder, :form, to: :@input
delegate :builder, :form, :select_options, to: :@input

def initialize(input:, autocomplete_options:)
super()
@input = input
@autocomplete_options = autocomplete_options
end

def decorated_select?
@autocomplete_options[:decorated]
end
end
end
end
Expand Down
27 changes: 26 additions & 1 deletion lib/primer/open_project/forms/dsl/autocompleter_input.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,39 @@ module OpenProject
module Forms
module Dsl
class AutocompleterInput < Primer::Forms::Dsl::Input
attr_reader :name, :label, :autocomplete_options
attr_reader :name, :label, :autocomplete_options, :select_options

class Option
attr_reader :label, :value, :selected

def initialize(label:, value:, selected: false)
@label = label
@value = value
@selected = selected
end

def to_h
{
label:,
value:,
selected:
}
end
end

def initialize(name:, label:, autocomplete_options:, **system_arguments)
@name = name
@label = label
@autocomplete_options = autocomplete_options
@select_options = []

super(**system_arguments)

yield(self) if block_given?
end

def option(**args)
@select_options << Option.new(**args)
end

def to_component
Expand Down
4 changes: 2 additions & 2 deletions lib/primer/open_project/forms/dsl/input_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ module OpenProject
module Forms
module Dsl
module InputMethods
def autocompleter(**)
add_input AutocompleterInput.new(builder: @builder, form: @form, **)
def autocompleter(**, &)
add_input AutocompleterInput.new(builder: @builder, form: @form, **, &)
end

def rich_text_area(**)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@
title = I18n.t(:label_added_time_by,
author: @meeting_agenda_item.author.name,
age: helpers.distance_of_time_in_words(Time.zone.now, @meeting_agenda_item.created_at))
render(Users::AvatarComponent.new(user: @meeting_agenda_item.author, size: 'mini', title:, classes: 'op-principal_flex'))
flex_layout do |flex|
flex.with_column do
render(Primer::Beta::Text.new(color: :subtle, mr: 1)) { t("label_added_by", author: nil) }
end
flex.with_column do
render(Users::AvatarComponent.new(user: @meeting_agenda_item.author, size: 'mini', title:, classes: 'op-principal_flex'))
end
end
end

grid.with_area(:actions, tag: :div, justify_self: :end) do
Expand Down
Loading

0 comments on commit 1a9d031

Please sign in to comment.