Skip to content

Commit

Permalink
Merge pull request opf#14639 from opf/implementation/51246-default-qu…
Browse files Browse the repository at this point in the history
…ery-for-new-gantt-module-2

[51246] Default query for new Gantt module
  • Loading branch information
HDinger authored Jan 31, 2024
2 parents 06ed844 + cf73c11 commit 1965c21
Show file tree
Hide file tree
Showing 16 changed files with 419 additions and 47 deletions.
50 changes: 26 additions & 24 deletions app/components/open_project/common/submenu_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,34 @@
<% nested_sidebar_menu_items = @sidebar_menu_items.filter { |menu_item| menu_item.header.present? } %>
<% if nested_sidebar_menu_items.any? %>
<% nested_sidebar_menu_items.each do |menu_item| %>
<div class="op-sidemenu"
data-controller="menus--expandable-sidemenu"
data-application-target="dynamic">
<% if menu_item.children.any? %>
<div class="op-sidemenu"
data-controller="menus--expandable-sidemenu"
data-application-target="dynamic">

<button class="op-sidemenu--title"
type="button"
data-action="click->menus--expandable-sidemenu#toggleContainer">
<%= menu_item.header %>
<span class="icon-small icon-arrow-up1"
aria-hidden="true"
data-menus--expandable-sidemenu-target="indicator">
</span>
</button>
<button class="op-sidemenu--title"
type="button"
data-action="click->menus--expandable-sidemenu#toggleContainer">
<%= menu_item.header %>
<span class="icon-small icon-arrow-up1"
aria-hidden="true"
data-menus--expandable-sidemenu-target="indicator">
</span>
</button>

<ul class="op-sidemenu--items"
data-menus--expandable-sidemenu-target="container">
<% menu_item.children.each do |child_item| %>
<li class="op-sidemenu--item">
<% selected = child_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= child_item.href %>">
<span class="op-sidemenu--item-title"><%= child_item.title %></span>
</a>
</li>
<% end %>
</ul>
</div>
<ul class="op-sidemenu--items"
data-menus--expandable-sidemenu-target="container">
<% menu_item.children.each do |child_item| %>
<li class="op-sidemenu--item">
<% selected = child_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= child_item.href %>">
<span class="op-sidemenu--item-title"><%= child_item.title %></span>
</a>
</li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
<% end %>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import { QueryFormResource } from 'core-app/features/hal/resources/query-form-re
import { WorkPackageStatesInitializationService } from './wp-states-initialization.service';
import { WorkPackagesListInvalidQueryService } from './wp-list-invalid-query.service';
import { WorkPackagesQueryViewService } from 'core-app/features/work-packages/components/wp-list/wp-query-view.service';
import { TurboElement } from 'core-typings/turbo';

export interface QueryDefinition {
queryParams:{ query_id?:string|null, query_props?:string|null };
Expand Down Expand Up @@ -255,6 +256,7 @@ export class WorkPackagesListService {
// Reload the query, and then reload the menu
this.reloadQuery(createdQuery).subscribe(() => {
this.states.changes.queries.next(createdQuery.id);
this.reloadSidemenu(createdQuery.id);
});

return createdQuery;
Expand All @@ -280,14 +282,21 @@ export class WorkPackagesListService {
.then(() => {
this.toastService.addSuccess(this.I18n.t('js.notice_successful_delete'));

let id;
if (query.project) {
id = query.project.href!.split('/').pop();
}
if (this.$state.$current.data.hardReloadOnBaseRoute) {

Check failure on line 285 in frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts

View workflow job for this annotation

GitHub Actions / eslint

[eslint] reported by reviewdog 🐶 Unsafe member access .hardReloadOnBaseRoute on an `any` value. Raw Output: {"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access .hardReloadOnBaseRoute on an `any` value.","line":285,"column":13,"nodeType":"MemberExpression","messageId":"unsafeMemberExpression","endLine":285,"endColumn":60}
const url = new URL(window.location.href);
url.search = '';
window.location.href = url.href;
} else {
let projectId;
if (query.project) {
projectId = query.project.href!.split('/').pop();

Check warning on line 292 in frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts

View workflow job for this annotation

GitHub Actions / eslint

[eslint] reported by reviewdog 🐶 Forbidden non-null assertion. Raw Output: {"ruleId":"@typescript-eslint/no-non-null-assertion","severity":1,"message":"Forbidden non-null assertion.","line":292,"column":25,"nodeType":"TSNonNullExpression","messageId":"noNonNull","endLine":292,"endColumn":44,"suggestions":[{"messageId":"suggestOptionalChain","fix":{"range":[11030,11031],"text":"?"},"desc":"Consider using the optional chain operator `?.` instead. This operator includes runtime checks, so it is safer than the compile-only non-null assertion operator."}]}
}

this.loadDefaultQuery(id);
void this.loadDefaultQuery(projectId);

this.states.changes.queries.next(query.id!);
this.states.changes.queries.next(query.id!);

Check warning on line 297 in frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts

View workflow job for this annotation

GitHub Actions / eslint

[eslint] reported by reviewdog 🐶 Forbidden non-null assertion. Raw Output: {"ruleId":"@typescript-eslint/no-non-null-assertion","severity":1,"message":"Forbidden non-null assertion.","line":297,"column":44,"nodeType":"TSNonNullExpression","messageId":"noNonNull","endLine":297,"endColumn":53}
this.reloadSidemenu(null);
}
});

return promise;
Expand All @@ -311,6 +320,7 @@ export class WorkPackagesListService {

this.$state.go('.', { query_id: query!.id, query_props: null }, { reload: true });
this.states.changes.queries.next(query!.id!);
this.reloadSidemenu(query.id);
})
.catch((error:ErrorResource) => {
this.toastService.addError(error.message);
Expand Down Expand Up @@ -338,6 +348,7 @@ export class WorkPackagesListService {
this.toastService.addSuccess(this.I18n.t('js.notice_successful_update'));

this.states.changes.queries.next(query.id!);
this.reloadSidemenu(query.id);
});

return promise;
Expand Down Expand Up @@ -423,4 +434,24 @@ export class WorkPackagesListService {
)),
);
}

private reloadSidemenu(selectedQueryId:string|null):void {
const menuIdentifier:string|undefined = this.$state.current.data.sidemenuId;

Check failure on line 439 in frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts

View workflow job for this annotation

GitHub Actions / eslint

[eslint] reported by reviewdog 🐶 Unsafe assignment of an `any` value. Raw Output: {"ruleId":"@typescript-eslint/no-unsafe-assignment","severity":2,"message":"Unsafe assignment of an `any` value.","line":439,"column":11,"nodeType":"VariableDeclarator","messageId":"anyAssignment","endLine":439,"endColumn":80}

Check failure on line 439 in frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts

View workflow job for this annotation

GitHub Actions / eslint

[eslint] reported by reviewdog 🐶 Unsafe member access .sidemenuId on an `any` value. Raw Output: {"ruleId":"@typescript-eslint/no-unsafe-member-access","severity":2,"message":"Unsafe member access .sidemenuId on an `any` value.","line":439,"column":45,"nodeType":"MemberExpression","messageId":"unsafeMemberExpression","endLine":439,"endColumn":80}

if (menuIdentifier) {
const menu = (document.getElementById(menuIdentifier) as HTMLElement&TurboElement);
const currentSrc = menu.getAttribute('src');

if (currentSrc && menu) {
const frameUrl = new URL(currentSrc);

// Override the frame src to enforce a reload
if (selectedQueryId) {
frameUrl.search = `?query_id=${selectedQueryId}`;
}

menu.setAttribute('src', frameUrl.href);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,30 @@ import { makeSplitViewRoutes } from 'core-app/features/work-packages/routing/spl

export const menuItemClass = 'gantt-menu-item';

export const sidemenuId = 'gantt_menu';

export const WORK_PACKAGES_GANTT_ROUTES:Ng2StateDeclaration[] = [
{
name: 'gantt',
parent: 'optional_project',
url: '/gantt?query_id&query_props&start_onboarding_tour',
url: '/gantt?query_id&query_props&name&start_onboarding_tour',
redirectTo: 'gantt.partitioned.list',
views: {
'!$default': { component: WorkPackagesBaseComponent },
},
data: {
bodyClasses: 'router--work-packages-base',
menuItem: menuItemClass,
sidemenuId,
hardReloadOnBaseRoute: true,
},
params: {
query_id: { type: 'query', dynamic: true },
// Use custom encoder/decoder that ensures validity of URL string
query_props: { type: 'opQueryString' },
// Optional initial tour param
start_onboarding_tour: { type: 'query', squash: true, value: undefined },
name: { type: 'string', dynamic: true },
},
},
{
Expand All @@ -63,6 +68,8 @@ export const WORK_PACKAGES_GANTT_ROUTES:Ng2StateDeclaration[] = [
data: {
// This has to be empty to avoid inheriting the parent bodyClasses
bodyClasses: '',
sidemenuId,
hardReloadOnBaseRoute: true,
},
},
{
Expand All @@ -76,6 +83,8 @@ export const WORK_PACKAGES_GANTT_ROUTES:Ng2StateDeclaration[] = [
bodyClasses: 'router--work-packages-partitioned-split-view',
menuItem: menuItemClass,
partition: '-left-only',
sidemenuId,
hardReloadOnBaseRoute: true,
},
},
...makeSplitViewRoutes(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ export class StaticQueriesService {
if (matched) {
return matched.title;
}

if (this.$state.params.name) {
const nameKey = this.$state.params.name as string;
return this.I18n.t(`js.queries.${nameKey}`);
}
}

// Try to detect the all open filter
Expand Down
16 changes: 16 additions & 0 deletions modules/gantt/app/controllers/gantt/gantt_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ class GanttController < ApplicationController

menu_item :gantt
def index
# If there are no query_props given, redirect to the default query
if params[:query_props].nil? && params[:query_id].nil?
if @project.present?
return redirect_to(
project_gantt_index_path(
@project,
::Gantt::DefaultQueryGeneratorService.new(with_project: @project).call
)
)
else
return redirect_to(
gantt_index_path(Gantt::DefaultQueryGeneratorService.new(with_project: nil).call)
)
end
end

respond_to do |format|
format.html do
render :index,
Expand Down
121 changes: 121 additions & 0 deletions modules/gantt/app/controllers/gantt/menus_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2010-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.
# ++
module Gantt
class MenusController < ApplicationController
before_action :find_optional_project

def show
@sidebar_menu_items = menu_items
render layout: nil
end

private

def menu_items
[
OpenProject::Menu::MenuGroup.new(header: I18n.t('js.label_starred_queries'), children: starred_queries),
OpenProject::Menu::MenuGroup.new(header: I18n.t('js.label_default_queries'), children: default_queries),
OpenProject::Menu::MenuGroup.new(header: I18n.t('js.label_global_queries'), children: global_queries),
OpenProject::Menu::MenuGroup.new(header: I18n.t('js.label_custom_queries'), children: custom_queries)
]
end

def starred_queries
base_query
.where('starred' => 't')
.pluck(:id, :name)
.map { |id, name| menu_item({ query_id: id }, name) }
end

def default_queries
query_generator = Gantt::DefaultQueryGeneratorService.new(with_project: @project)
queries = []
Gantt::DefaultQueryGeneratorService::QUERY_OPTIONS.each do |query_key|
queries << menu_item(
query_generator.call(query_key:),
Gantt::DefaultQueryGeneratorService::QUERY_MAPPINGS[query_key]
)
end

queries
end

def global_queries
base_query
.where('starred' => 'f')
.where('public' => 't')
.pluck(:id, :name)
.map { |id, name| menu_item({ query_id: id }, name) }
end

def custom_queries
base_query
.where('starred' => 'f')
.where('public' => 'f')
.pluck(:id, :name)
.map { |id, name| menu_item({ query_id: id }, name) }
end

def base_query
base_query ||= Query
.visible(current_user)
.joins(:views, :project)
.where('views.type' => 'gantt')

if @project.present?
base_query = base_query.where('queries.project_id' => @project.id)
end

base_query
end

def menu_item(query_params, name)
OpenProject::Menu::MenuItem.new(title: name,
href: gantt_path(query_params),
selected: selected?(query_params))
end

def selected?(query_params)
query_params.each_key do |filter_key|
if params[filter_key] != query_params[filter_key].to_s
return false
end
end

true
end

def gantt_path(query_params)
if @project.present?
project_gantt_index_path(@project, params.permit(query_params.keys).merge!(query_params))
else
gantt_index_path(params.permit(query_params.keys).merge!(query_params))
end
end
end
end
Loading

0 comments on commit 1965c21

Please sign in to comment.