Merge branch 'release/16.1' into dev

This commit is contained in:
OpenProject Actions CI
2025-06-11 11:56:44 +00:00
13 changed files with 51 additions and 146 deletions
@@ -144,6 +144,21 @@ If you want to be reminded about a work package at a later point in time, you ca
![Work package reminder icon in OpenProject](openproject_user_guide_wp_reminder_icon.png)
A list with helpful pre-defined options will open, from which you can select:
- tomorrow
- in 3 days
- in a week
- in a month
- at a particular date/time
Selecting any of these options will display a modal. The time will be set to 9 am for the date you selected (apart from the last option). This modal allows you to adjust the pre-filled date and time and to add a note. This note will be visible when the reminder is triggered in Notification center.
> [!TIP]
> All the pre-defined reminder options will be set to 9 am of the selected date.
![Pre-defined options for a work package reminder in OpenProject](openproject_user_guide_wp_reminder_quick_options.png)
Specify the time and date on which you would like to be reminded and optionally add a note for more context. Then click the **Set reminder** button.
![Set reminder in OpenProject work packages](openproject_user_guide_wp_set_reminder.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

+2
View File
@@ -218,6 +218,7 @@ import {
import {
OpWpDatePickerInstanceComponent,
} from 'core-app/shared/components/datepicker/wp-date-picker-modal/wp-date-picker-instance.component';
import { ReportingPageComponent } from 'core-app/features/reporting/reporting-page/reporting-page.component';
export function initializeServices(injector:Injector) {
return () => {
@@ -462,5 +463,6 @@ export class OpenProjectModule implements DoBootstrap {
registerCustomElement('opce-global-search-tabs', GlobalSearchTabsComponent, { injector });
registerCustomElement('opce-zen-mode-toggle-button', ZenModeButtonComponent, { injector });
registerCustomElement('opce-colors-autocompleter', ColorsAutocompleterComponent, { injector });
registerCustomElement('opce-reporting-page', ReportingPageComponent, { injector });
}
}
@@ -80,12 +80,6 @@ export const OPENPROJECT_ROUTES:Ng2StateDeclaration[] = [
url: '/bcf',
loadChildren: () => import('../../features/bim/ifc_models/openproject-ifc-models.module').then((m) => m.OpenprojectIFCModelsModule),
},
{
name: 'reporting.**',
parent: 'optional_project',
url: '/cost_reports',
loadChildren: () => import('../../features/reporting/openproject-reporting.module').then((m) => m.OpenprojectReportingModule),
},
...TEAM_PLANNER_LAZY_ROUTES,
...CALENDAR_LAZY_ROUTES,
];
@@ -1,48 +0,0 @@
//-- 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.
//++
import { NgModule } from '@angular/core';
import { UIRouterModule } from '@uirouter/angular';
import {
REPORTING_ROUTES,
} from 'core-app/features/reporting/openproject-reporting.routes';
import { ReportingPageComponent } from 'core-app/features/reporting/reporting-page/reporting-page.component';
@NgModule({
imports: [
// Routes for /cost_reports
UIRouterModule.forChild({
states: REPORTING_ROUTES,
}),
],
declarations: [
ReportingPageComponent,
],
})
export class OpenprojectReportingModule {
}
@@ -1,44 +0,0 @@
//-- 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.
//++
import { Ng2StateDeclaration } from '@uirouter/angular';
import { ReportingPageComponent } from 'core-app/features/reporting/reporting-page/reporting-page.component';
export const REPORTING_ROUTES:Ng2StateDeclaration[] = [
{
name: 'reporting',
parent: 'optional_project',
url: '/cost_reports',
component: ReportingPageComponent,
},
{
name: 'reporting.show',
url: '/:id',
component: ReportingPageComponent,
},
];
@@ -26,6 +26,8 @@
// See COPYRIGHT and LICENSE files for more details.
//++
import * as jQuery from 'jquery';
/*jslint white: false, nomen: true, devel: true, on: true, debug: false, evil: true, onevar: false, browser: true, white: false, indent: 2 */
/*global window, $, $$, Reporting, Element */
@@ -1,16 +1,7 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { registerTableSorter } from 'core-app/features/reporting/reporting-page/functionality/tablesorter';
import './functionality/reporting_engine';
import './functionality/reporting_engine/filters';
import './functionality/reporting_engine/group_bys';
import './functionality/reporting_engine/restore_query';
import './functionality/reporting_engine/controls';
export const reportingPageComponentSelector = 'op-reporting-page';
@Component({
selector: reportingPageComponentSelector,
// Empty wrapper around legacy backlogs for CSS loading
// that got removed in the Rails assets pipeline
encapsulation: ViewEncapsulation.None,
@@ -18,10 +9,20 @@ export const reportingPageComponentSelector = 'op-reporting-page';
styleUrls: [
'./styles/reporting.sass',
],
standalone: true,
})
export class ReportingPageComponent implements OnInit {
ngOnInit() {
document.getElementById('projected-content')!.hidden = false;
async ngOnInit() {
// @ts-expect-error imported JS is not typed
await import('./functionality/reporting_engine');
// @ts-expect-error imported JS is not typed
await import('./functionality/reporting_engine/filters');
// @ts-expect-error imported JS is not typed
await import('./functionality/reporting_engine/group_bys');
// @ts-expect-error imported JS is not typed
await import('./functionality/reporting_engine/restore_query');
// @ts-expect-error imported JS is not typed
await import('./functionality/reporting_engine/controls');
// Register table sorting functionality after reporting engine loaded
registerTableSorter();
@@ -53,7 +53,9 @@ module Meetings::PDF
def write_section(section, section_index)
write_optional_page_break
write_section_title(section, section_index) unless section.title.empty?
unless section.title.blank? && section_index == 0 && meeting.sections.count == 1
write_section_title(section, section_index)
end
write_agenda_items(section)
end
@@ -130,12 +130,20 @@ RSpec.describe Meetings::Exporter do
let(:type_task) { create(:type_task) }
let(:status) { create(:status, is_default: true, name: "Workin' on it") }
let(:work_package) { create(:work_package, project:, status:, subject: "Important task", type: type_task) }
let(:meeting_section) { create(:meeting_section, meeting:, title: "Section Work in Progress") }
let(:meeting_section) { create(:meeting_section, meeting:, title: nil) }
let(:meeting_section_second) { create(:meeting_section, meeting:, title: "Second section") }
let(:meeting_agenda_item) do
create(:meeting_agenda_item, meeting_section:, duration_in_minutes: 15, title: "Agenda Item TOP 1", presenter: user,
notes: "**foo**")
end
let(:wp_agenda_item) { create(:wp_meeting_agenda_item, meeting:, work_package:, duration_in_minutes: 10, notes: "*bar*") }
let(:wp_agenda_item) do
create(:wp_meeting_agenda_item,
meeting:,
meeting_section: meeting_section_second,
work_package:,
duration_in_minutes: 10,
notes: "*bar*")
end
let(:outcome) { create(:meeting_outcome, meeting_agenda_item:, notes: "An outcome") }
let(:attachment) { create(:attachment, container: meeting) }
let(:meeting_backlog_item) do
@@ -173,13 +181,15 @@ RSpec.describe Meetings::Exporter do
"Meeting agenda",
"Section Work in Progress", " ", "25 mins",
"Untitled section", " ", "15 mins",
"Agenda Item TOP 1", " ", "15 mins", " ", "Export User",
"foo",
"Outcome",
"An outcome",
"Second section", " ", "10 mins",
"Task", "##{work_package.id}", "Important task", " (Workin' on it)", " ", "10 mins",
"bar",
@@ -215,11 +225,11 @@ RSpec.describe Meetings::Exporter do
*single_meeting_head,
"Meeting agenda",
"Section Work in Progress", " ", "25 mins",
"Untitled section", " ", "15 mins",
"Agenda Item TOP 1", " ", "15 mins", " ", "Export User",
"foo",
"Second section", " ", "10 mins",
"Task", "##{work_package.id}", "Important task", " (Workin' on it)", " ", "10 mins",
"bar",
@@ -67,8 +67,6 @@ class CostReportsController < ApplicationController
before_action :set_cost_types # has to be set AFTER the Report::Controller filters run
layout "angular/angular"
# Checks if custom fields have been updated, added or removed since we
# last saw them, to rebuild the filters and group bys.
# Called once per request.
@@ -35,6 +35,8 @@ See COPYRIGHT and LICENSE files for more details.
<% html_title (@query.persisted? ? "#{t(:label_cost_report)}: #{@query.name}" : t(:label_new_report)) %>
<%= angular_component_tag "opce-reporting-page" %>
<%= render CostReports::IndexPageHeaderComponent.new(query: @query, project: @project) %>
<%= render_widget Widget::Settings, @query, cost_types: @cost_types, selected_type_id: @unit_id %>
@@ -85,34 +85,5 @@ RSpec.describe "project menu" do
it_behaves_like "it leads to the project costs reports"
end
end
describe "link to global cost reports" do
shared_examples "it leads to the cost reports" do
before do
visit current_path
end
it "leads to cost reports" do
# doing what no human can - click on invisible items.
# This way, we avoid having to use selenium and by that increase stability.
find("#main-menu #{test_selector('op-menu--item-action')}", text: "Time and costs").click
# to make sure we're not seeing the project cost reports:
expect(page).to have_no_text("Ponyo")
end
end
context "when on the project's activity page" do
let(:current_path) { "/projects/ponyo/activity" }
it_behaves_like "it leads to the cost reports"
end
context "when on the project's calendar" do
let(:current_path) { "/projects/ponyo/calendars" }
it_behaves_like "it leads to the cost reports"
end
end
end
end