Skip to content
Merged
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
5 changes: 0 additions & 5 deletions app/contracts/roles/base_contract.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ def assignable_member_permissions
[]
end

# For now, we also remove all permissions related to resource management as this module is still behind FF
unless Rails.env.local?
permissions_to_remove += OpenProject::AccessControl.module_permissions(:resource_management)
end

OpenProject::AccessControl.project_permissions - permissions_to_remove
end

Expand Down
15 changes: 14 additions & 1 deletion app/controllers/projects/settings/creation_wizard_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,16 @@ def disable_dialog
end

def toggle
@project.update(project_creation_wizard_enabled: !@project.project_creation_wizard_enabled)
enabling = !@project.project_creation_wizard_enabled
@project.update!(project_creation_wizard_enabled: enabling)

# Enabling PIR will enable all the project's currently active
# project attributes for PIR by default.
# Note that project attributes that are added to the project *later*
# are not enabled in PIR automatically.
# They will have to be enabled explicitly on the attributes tab.
enable_creation_wizard_for_all_mappings! if enabling

redirect_to project_settings_creation_wizard_path(@project, tab: params[:tab]), status: :see_other
end

Expand Down Expand Up @@ -139,6 +148,10 @@ def enable_creation_wizard!(custom_field_ids)
.update_all(creation_wizard: true)
end

def enable_creation_wizard_for_all_mappings!
@project.project_custom_field_project_mappings.update_all(creation_wizard: true)
end

def custom_field_toggleable?(custom_field)
toggleable_ids = ProjectCustomField
.toggleable_ids_in_creation_wizard_settings(@project, custom_field.custom_field_section_id)
Expand Down
31 changes: 15 additions & 16 deletions app/services/projects/copy_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def before_perform(service_call)
def after_perform(call)
super.tap do |super_call|
copy_activated_custom_fields(super_call)
copy_creation_wizard_flags(super_call.result)
update_calculated_value_custom_fields(super_call.result)
end
end
Expand All @@ -118,6 +119,20 @@ def copy_activated_custom_fields(call)
call.result.project_custom_field_ids = source.project_custom_field_ids
end

# Activating a custom field on the copy must not silently enable it for
# the creation wizard (PIR) - unless the source project already had it
# enabled, in which case we want to preserve that setting on the copy.
# This has to run after copy_activated_custom_fields, since that is what
# actually creates most of the mappings being adjusted here.
def copy_creation_wizard_flags(project)
source_flags = source.project_custom_field_project_mappings.pluck(:custom_field_id, :creation_wizard).to_h

project.project_custom_field_project_mappings.find_each do |mapping|
creation_wizard = source_flags[mapping.custom_field_id]
mapping.update_column(:creation_wizard, creation_wizard) unless creation_wizard.nil?
end
end

def retain_attributes(source, target)
# Ensure we keep the public value of the source project
# which might get overridden by the SetAttributesService
Expand Down Expand Up @@ -178,21 +193,5 @@ def only_allowed_parent_id(attributes)
attributes
end
end

private

def build_missing_project_custom_field_project_mappings(project)
# Build mappings using the concern's logic
super

# Copy creation_wizard flag from source project's mappings to the newly built mappings
source_mappings_by_custom_field_id = source.project_custom_field_project_mappings
.index_by(&:custom_field_id)

project.project_custom_field_project_mappings.each do |mapping|
source_mapping = source_mappings_by_custom_field_id[mapping.custom_field_id]
mapping.creation_wizard = source_mapping.creation_wizard if source_mapping
end
end
end
end
1 change: 1 addition & 0 deletions app/workers/apply_working_days_change_job_base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
# Common methods for ApplyWorkingDaysChangeJobs
class ApplyWorkingDaysChangeJobBase < ApplicationJob
include JobConcurrency

queue_with_priority :above_normal

good_job_control_concurrency_with(
Expand Down
7 changes: 6 additions & 1 deletion app/workers/job_concurrency.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,12 @@ def check_concurrency
end

limit = enqueue_limit || total_limit
enqueued_jobs = GoodJob::Job.where(concurrency_key: good_job_concurrency_key).unfinished.advisory_unlocked.count
# Regression #OP-19861
# Count ALL unfinished jobs, including ones currently performing. Excluding
# advisory-locked (running) jobs allowed a second settings save while
# ApplyWorkingDaysChangeJob was still running; GoodJob then aborted enqueue
# of the follow-up job.
enqueued_jobs = GoodJob::Job.where(concurrency_key: good_job_concurrency_key).unfinished.count

yield if limit.present? && enqueued_jobs + 1 > limit
end
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# frozen_string_literal: true

#-- 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.
#++

# Newly activating a project attribute for a project must not automatically
# enable it for the project creation wizard (PIR) anymore. Enabling it has to
# be a conscious, separate step. Enabling the wizard itself still activates
# all of a project's currently active attributes, but that is now done
# explicitly in Projects::Settings::CreationWizardController#toggle instead of
# relying on this default.
class ChangeCreationWizardDefaultOnProjectCustomFieldProjectMappings < ActiveRecord::Migration[8.0]
def change
change_column_default :project_custom_field_project_mappings, :creation_wizard, from: true, to: false
end
end
2 changes: 1 addition & 1 deletion docker/prod/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ ENV OPENPROJECT_RAILS__CACHE__STORE=memcache
ENV DATABASE_URL=postgres://openproject:openproject@127.0.0.1/openproject
ENV PGDATA=/var/openproject/pgdata

COPY --from=openproject/hocuspocus:17.7.0 --chown=$APP_USER:$APP_USER /app /opt/hocuspocus
COPY --from=openproject/hocuspocus:17.7.1 --chown=$APP_USER:$APP_USER /app /opt/hocuspocus
# Keep node/npm in all-in-one for bundled hocuspocus even when BIM support is disabled.
COPY --from=build-base /usr/local/bin/node /usr/local/bin/node
COPY --from=build-base /usr/local/lib/node_modules /usr/local/lib/node_modules
Expand Down
31 changes: 31 additions & 0 deletions docs/release-notes/17-7-1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
title: OpenProject 17.7.1
sidebar_navigation:
title: 17.7.1
release_version: 17.7.1
release_date: 2026-08-06
---

# OpenProject 17.7.1

Release date: 2026-08-06

We released [OpenProject 17.7.1](https://community.openproject.org/versions/2318).
The release contains several bug fixes and we recommend updating to the newest version.
Below you will find a complete list of all changes and bug fixes.
<!-- BEGIN SECURITY FIXES AUTOMATED SECTION -->
<!-- END SECURITY FIXES AUTOMATED SECTION -->
<!--more-->

## Bug fixes and changes

<!-- Warning: Anything within the below lines will be automatically removed by the release script -->
<!-- BEGIN AUTOMATED SECTION -->

- Bugfix: Retrying a stopped import creates duplicate OpenProject users \[[#78165](https://community.openproject.org/wp/78165)\]
- Bugfix: Jira attachment authors are not created as users \[[#78168](https://community.openproject.org/wp/78168)\]
- Bugfix: Resource management permissions not visible under Roles &gt; Administration \[[#78202](https://community.openproject.org/wp/78202)\]
- Bugfix: Wrong activation of project attributes in PIR \[[#78153](https://community.openproject.org/wp/78153)\]

<!-- END AUTOMATED SECTION -->
<!-- Warning: Anything above this line will be automatically removed by the release script -->
7 changes: 7 additions & 0 deletions docs/release-notes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ Stay up to date and get an overview of the new features included in the releases
<!--- New release notes are generated below. Do not remove comment. -->
<!--- RELEASE MARKER -->

## 17.7.1

Release date: 2026-08-06

[Release Notes](17-7-1/)


## 17.7.0

Release date: 2026-08-05
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,5 +553,16 @@ describe('ProjectTimelineGraphComponent', () => {
expect(element.querySelector('ul.sr-only')?.textContent).toContain('Phase gate Build Start: 2024-04-01');
expect(element.querySelector('ul.sr-only')?.textContent).toContain('Phase gate Build End: 2024-06-30');
});

it('hides the loading skeleton once the initial draw completes', async () => {
const element = fixture.nativeElement as HTMLElement;

await vi.waitUntil(() => {
fixture.detectChanges();
return element.querySelector('.op-project-timeline-graph--wrapper_loading') === null;
});

expect(element.querySelector('.op-project-timeline-graph--wrapper_loading')).toBeNull();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,19 @@
//++

import {
AfterViewInit,
ChangeDetectionStrategy,
Component,
DestroyRef,
ElementRef,
OnDestroy,
ViewChild,
ViewEncapsulation,
afterNextRender,
computed,
effect,
inject,
input,
signal,
} from '@angular/core';
import { Subject } from 'rxjs';
import { debounceTime, take, takeUntil } from 'rxjs/operators';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { OpenprojectContentLoaderModule } from 'core-app/shared/components/op-content-loader/openproject-content-loader.module';
import { DataSet } from 'vis-data';
Expand All @@ -65,7 +63,7 @@ export type { ProjectTimelineItem } from './project-timeline-item.builder';
imports: [OpenprojectContentLoaderModule],
providers: [ProjectTimelineItemBuilder, ProjectTimelineTooltipBuilder],
})
export class ProjectTimelineGraphComponent implements AfterViewInit, OnDestroy {
export class ProjectTimelineGraphComponent {
@ViewChild('container') containerRef!:ElementRef<HTMLDivElement>;

readonly phasesData = input.required<string>();
Expand Down Expand Up @@ -96,11 +94,14 @@ export class ProjectTimelineGraphComponent implements AfterViewInit, OnDestroy {
private itemsDataset:DataSet<ProjectTimelineItem> | null = null;

protected readonly ready = signal(false);
private destroyed = false;
private readyHandler:(() => void) | null = null;
private readonly destroyed$ = new Subject<void>();

constructor() {
afterNextRender(() => this.initTimeline(this.phases(), this.milestones(), this.sprints()));
inject(DestroyRef).onDestroy(() => {
this.timeline?.destroy();
this.timeline = null;
});

effect(() => {
const phases = this.phases();
const milestones = this.milestones();
Expand All @@ -111,24 +112,6 @@ export class ProjectTimelineGraphComponent implements AfterViewInit, OnDestroy {
});
}

ngAfterViewInit():void {
requestAnimationFrame(() => {
if (!this.destroyed) {
this.initTimeline(this.phases(), this.milestones(), this.sprints());
}
});
}

ngOnDestroy():void {
this.destroyed = true;
this.destroyed$.next();
this.destroyed$.complete();

if (this.readyHandler) this.timeline?.off('changed', this.readyHandler);
this.timeline?.destroy();
this.timeline = null;
}

private initTimeline(phases:ProjectPhaseData[], milestones:ProjectMilestoneData[], sprints:ProjectSprintData[]):void {
const { items, groups } = this.itemBuilder.buildData(phases, milestones, sprints);
this.itemsDataset = new DataSet(items);
Expand All @@ -146,9 +129,10 @@ export class ProjectTimelineGraphComponent implements AfterViewInit, OnDestroy {
showMajorLabels: true,
showMinorLabels: true,
margin: { item: { horizontal: 0, vertical: 16 } },
showCurrentTime: false, // enabled after reveal; avoids periodic changed events interfering with the ready debounce
showCurrentTime: false, // enabled after the initial draw to avoid unnecessary redraws while loading
zoomMin: 7 * 24 * 60 * 60 * 1000, // 7 days minimum zoom
zoomMax: 50 * 365 * 24 * 60 * 60 * 1000, // 50 years maximum zoom
onInitialDrawComplete: () => this.revealTimeline(),
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment
tooltip: { template: this.tooltip.tooltipTemplate.bind(this.tooltip), overflowMethod: 'cap' } as any,
},
Expand All @@ -161,8 +145,6 @@ export class ProjectTimelineGraphComponent implements AfterViewInit, OnDestroy {
window.location.href = this.pathHelper.workPackagePath(String(item.workPackageId));
}
});

this.revealWhenReady();
}

private updateTimeline(phases:ProjectPhaseData[], milestones:ProjectMilestoneData[], sprints:ProjectSprintData[]):void {
Expand All @@ -171,27 +153,14 @@ export class ProjectTimelineGraphComponent implements AfterViewInit, OnDestroy {
this.timeline!.setData({ items: this.itemsDataset as unknown as DataSet<DataItem>, groups: new DataSet(groups) });
}

// Hides the skeleton once vis-timeline has stopped firing 'changed' events.
// Multiple render passes occur on an initial load, so we debounce.
private revealWhenReady():void {
const changed$ = new Subject<void>();

this.readyHandler = () => changed$.next();
this.timeline!.on('changed', this.readyHandler);

changed$.pipe(
debounceTime(1000),
take(1),
takeUntil(this.destroyed$),
).subscribe(() => {
this.timeline!.off('changed', this.readyHandler!);
this.readyHandler = null;
this.timeline!.setOptions({
showCurrentTime: true,
cluster: { maxItems: 1, clusterCriteria: this.shouldCluster.bind(this) },
});
this.ready.set(true);
private revealTimeline():void {
if (!this.timeline) return;

this.timeline.setOptions({
showCurrentTime: true,
cluster: { maxItems: 1, clusterCriteria: this.shouldCluster.bind(this) },
});
this.ready.set(true);
}

private shouldCluster(a:ProjectTimelineItem, b:ProjectTimelineItem):boolean {
Expand Down
4 changes: 2 additions & 2 deletions publiccode.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ name: OpenProject
applicationSuite: openDesk
url: 'https://github.com/opf/openproject'
roadmap: 'https://www.openproject.org/roadmap'
releaseDate: '2026-08-05'
softwareVersion: '17.7.0'
releaseDate: '2026-08-06'
softwareVersion: '17.7.1'
developmentStatus: stable
softwareType: standalone/web
logo: 'publiccode_logo.svg'
Expand Down
Loading
Loading