Skip to content

Commit

Permalink
Merge pull request opf#15162 from opf/feature/meetings-index-api
Browse files Browse the repository at this point in the history
Add /api/v3/meetings index action
  • Loading branch information
oliverguenther authored Apr 16, 2024
2 parents 65c5fec + 98264e8 commit 38a7821
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 19 deletions.
11 changes: 9 additions & 2 deletions app/components/filters_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<div data-controller="filters"
data-application-target="dynamic"
data-filters-output-format-value="<%= output_format %>"
data-filters-display-filters-value="<%= show_filters_section? %>">

<div class="op-filters-header">
Expand Down Expand Up @@ -31,8 +32,9 @@
data-action="filters#toggleDisplayFilters"></a>
<legend><%= t(:label_filter_plural) %></legend>
<ul class="advanced-filters--filters">
<% each_filter do |filter, filter_active| %>
<% each_filter do |filter, filter_active, additional_options| %>
<% filter_boolean = filter.is_a?(Queries::Filters::Shared::BooleanFilter) %>
<% autocomplete_filter = additional_options.key?(:autocomplete_options) %>

<li class="advanced-filters--filter <%= filter_active ? '' : 'hidden' %>"
filter-name="<%= filter.name %>"
Expand All @@ -58,7 +60,12 @@
} %>
<% end %>
<% value_visibility = operators_without_values.include?(selected_operator) ? 'hidden' : '' %>
<% if filter_boolean %>
<% if autocomplete_filter %>
<%= render partial: 'filters/autocomplete',
locals: { value_visibility: value_visibility,
filter: filter,
autocomplete_options: additional_options[:autocomplete_options] } %>
<% elsif filter_boolean %>
<%= render partial: 'filters/boolean',
locals: { value_visibility: value_visibility,
filter: filter } %>
Expand Down
43 changes: 35 additions & 8 deletions app/components/filters_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

class FiltersComponent < ApplicationComponent
options :query
options output_format: "params"

renders_many :buttons, lambda { |**system_arguments|
system_arguments[:ml] ||= 2
Expand All @@ -43,15 +44,11 @@ def show_filters_section?
# Returns filters, active and inactive.
# In case a filter is active, the active one will be preferred over the inactive one.
def each_filter
allowed_filters.map do |filter|
allowed_filters.each do |filter|
active_filter = query.find_active_filter(filter.name)
filter_active = active_filter.present?
additional_attributes = additional_filter_attributes(filter)

if filter_active
yield active_filter, filter_active
else
yield filter, filter_active
end
yield active_filter.presence || filter, active_filter.present?, additional_attributes
end
end

Expand All @@ -61,6 +58,36 @@ def allowed_filters
end

def filters_count
query.filters.count
@filters_count ||= query
.filters
.map(&:class)
.uniq
.count
end

protected

# With this method we can pass additional options for each type of filter into the frontend. This is especially
# useful when we want to pass options for the autocompleter components.
#
# When the method is overwritten in a subclass, the subclass should call super(filter) to get the default attributes.
#
# @param filter [QueryFilter] the filter for which we want to pass additional attributes
# @return [Hash] the additional attributes for the filter, that will be yielded in the each_filter method
def additional_filter_attributes(filter)
case filter
when Queries::Filters::Shared::ProjectFilter
{
autocomplete_options: {
component: "opce-project-autocompleter",
resource: "projects",
filters: [
{ name: "active", operator: "=", values: ["t"] }
]
}
}
else
{}
end
end
end
2 changes: 1 addition & 1 deletion app/models/queries/filters/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def initialize(name, options = {})
# Treat the constructor as private, as the filter MAY need to check
# the options before accepting them as a filter.
#
# Use +#create+ instead.
# Use +#create!+ instead.
private_class_method :new

##
Expand Down
16 changes: 16 additions & 0 deletions app/views/filters/_autocomplete.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<div class="advanced-filters--filter-value <%= value_visibility %>">
<%= angular_component_tag autocomplete_options[:component],
inputs: {
inputName: 'value',
multiple: true,
multipleAsSeparateInputs: false,
inputValue: filter.values
}.merge(autocomplete_options.except(:component)),
class: 'form--field',
data: {
'filter-autocomplete': true,
'filters-target': 'filterValueContainer',
'filter-name': filter.name
}
%>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<form
class="spot-modal loading-indicator--location"
data-indicator-name="modal"
name="modalSaveForm"
(submit)="saveQueryAs($event)"
>
<h1 id="spotModalTitle" class="spot-modal--header">{{text.save_as}}</h1>

<div class="spot-divider"></div>

<div
name="modalSaveForm"
class="spot-modal--body spot-container"
>
<div class="form--field -required">
<label class="form--label -bold" for="save-query-name" [textContent]="text.label_name"></label>
<div class="form--field-container">
<div class="form--text-field-container">
<input
class="form--text-field"
type="text"
name="save-query-name"
id="save-query-name"
#queryNameField
[(ngModel)]="queryName"
required
/>
</div>
</div>
</div>
<div class="spot-divider"></div>

<h2 class="spot-subheader-extra-small">{{ text.label_visibility_settings }}</h2>

<query-sharing-form
[isSave]="true"
(onChange)="setValues($event)"
[isStarred]="isStarred"
[isPublic]="isPublic">
</query-sharing-form>
</div>

<div class="spot-action-bar">
<div class="spot-action-bar--right">
<button
type="button"
class="button button_no-margin spot-modal--cancel-button spot-action-bar--action"
[textContent]="text.button_cancel"
(click)="closeMe($event)"
></button>
<button
class="button button_no-margin -primary -with-icon icon-save spot-action-bar--action"
[textContent]="text.button_save"
[disabled]="isBusy || !queryName"
></button>
</div>
</div>
</form>
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// -- copyright
// OpenProject is an open source project management software.
// Copyright (C) 2012-2024 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 { ChangeDetectorRef, Component, ElementRef, Inject, ViewChild } from '@angular/core';
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { OpModalComponent } from 'core-app/shared/components/modal/modal.component';
import { OpModalLocalsToken } from 'core-app/shared/components/modal/modal.service';
import { OpModalLocalsMap } from 'core-app/shared/components/modal/modal.types';
import { QuerySharingChange } from 'core-app/shared/components/modals/share-modal/query-sharing-form.component';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { WorkPackagesListService } from 'core-app/features/work-packages/components/wp-list/wp-list.service';
import { States } from 'core-app/core/states/states.service';
import { WorkPackagesQueryViewService } from 'core-app/features/work-packages/components/wp-list/wp-query-view.service';

@Component({
templateUrl: './view-settings.modal.html',
})
export class ViewSettingsModalComponent extends OpModalComponent {
public queryName = '';

public isStarred = false;

public isPublic = false;

public isBusy = false;

@ViewChild('queryNameField', { static: true }) queryNameField:ElementRef;

public text = {
title: this.I18n.t('js.modals.form_submit.title'),
text: this.I18n.t('js.modals.form_submit.text'),
save_as: this.I18n.t('js.label_save_as'),
label_name: this.I18n.t('js.modals.label_name'),
label_visibility_settings: this.I18n.t('js.label_visibility_settings'),
button_save: this.I18n.t('js.modals.button_save'),
button_cancel: this.I18n.t('js.button_cancel'),
close_popup: this.I18n.t('js.close_popup_title'),
};

constructor(
readonly elementRef:ElementRef,
@Inject(OpModalLocalsToken) public locals:OpModalLocalsMap,
readonly I18n:I18nService,
readonly states:States,
readonly querySpace:IsolatedQuerySpace,
readonly wpListService:WorkPackagesListService,
readonly wpView:WorkPackagesQueryViewService,
readonly halNotification:HalResourceNotificationService,
readonly cdRef:ChangeDetectorRef,
readonly toastService:ToastService,
) {
super(locals, cdRef, elementRef);
}

public setValues(change:QuerySharingChange):void {
this.isStarred = change.isStarred;
this.isPublic = change.isPublic;
}

public onOpen():void {
this.queryNameField.nativeElement.focus();
}

public get afterFocusOn():HTMLElement {
return document.getElementById('work-packages-settings-button') as HTMLElement;
}

public saveQueryAs($event:Event):void {
$event.preventDefault();

if (this.isBusy || !this.queryName) {
return;
}

this.isBusy = true;
const query = this.querySpace.query.value!;
query.public = this.isPublic;

this.wpListService
.create(query, this.queryName)
.then((savedQuery:QueryResource):Promise<any> => {
if (this.isStarred && !savedQuery.starred) {
return this.wpListService.toggleStarred(savedQuery).then(() => this.closeMe($event));
}

this.closeMe($event);
return Promise.resolve(true);
})
.catch((error:any) => this.halNotification.handleRawError(error))
.then(() => this.isBusy = false); // Same as .finally()
}
}
Loading

0 comments on commit 38a7821

Please sign in to comment.