Skip to content

Commit 6c30dc1

Browse files
authored
Merge pull request #20980 from opf/bug/68726-selected-project-phase-doesn-t-show-on-wp-form-if-it-contains-letters-from-latin-extended-set-or-cyrillic-alphabet
[#68726] Selected project phase doesnt show on wp form if it contains invalid letters
2 parents 3ec4000 + beb80e7 commit 6c30dc1

File tree

4 files changed

+23
-30
lines changed

4 files changed

+23
-30
lines changed

app/helpers/colors_helper.rb

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,12 @@ def resources_scope_color_css(name, scope, inline_foreground: false)
6868
end
6969

7070
# Render the highlighting color for the project phase definition.
71-
# On top of the normal classes as done for other resources, we also add a class based on the
72-
# base64 encoded name of the project phase definition.
73-
#
74-
# The class name is based on the project phase definition's name, which is guaranteed to be unique.
75-
#
76-
# That way the frontend does not have to load the definitions to get the color.
77-
# The = signs at the end of the base64 string are replaced with _ to make it a valid class name.
78-
# This needs to be kept in sync with the ProjectPhaseDisplayField#phaseIcon method in the front end.
7971
def project_phase_color_css
8072
Project::PhaseDefinition.includes(:color).find_each do |definition|
8173
resource_color_css("project_phase_definition", definition, inline_foreground: true)
8274

8375
set_foreground_colors_for(
84-
class_name: ".#{hl_inline_class('project_phase_definition', Base64.strict_encode64(definition.name).tr('=', '_'))}",
76+
class_name: ".#{hl_inline_class('project_phase_definition', definition.id)}",
8577
color: definition.color
8678
)
8779
end

frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/grouped/group-header-builder.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decora
3535
import { GroupObject } from 'core-app/features/hal/resources/wp-collection-resource';
3636
import { groupName } from './grouped-rows-helpers';
3737
import { ProjectPhaseDisplayField } from 'core-app/shared/components/fields/display/field-types/project-phase-display-field.module';
38+
import idFromLink from 'core-app/features/hal/helpers/id-from-link';
3839

3940
export function groupClassNameFor(group:GroupObject) {
4041
return `group-${group.identifier}`;
@@ -98,13 +99,14 @@ export class GroupHeaderBuilder {
9899
* @private
99100
*/
100101
private leadingIcon(group:GroupObject) {
101-
const isProjectPhase = group.href.some((item) =>
102-
// will also match project phase definitions
103-
item.href !== null && item.href.includes('api/v3/project_phase'));
102+
// will also match project phase definitions
103+
const groupLink = group.href.find(
104+
(item) =>item.href?.includes('api/v3/project_phase')
105+
);
104106

105-
if (isProjectPhase) {
106-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
107-
return ProjectPhaseDisplayField.phaseIconByName(groupName(group), false);
107+
if (groupLink) {
108+
const definitionId = idFromLink(groupLink.href);
109+
return ProjectPhaseDisplayField.phaseIconById(definitionId, false);
108110
}
109111

110112
return null;

frontend/src/app/shared/components/fields/display/field-types/project-phase-display-field.module.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import { opPhaseIconData, toDOMString } from '@openproject/octicons-angular';
3232
import { DisplayField } from 'core-app/shared/components/fields/display/display-field.module';
33+
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
3334
import { ProjectPhaseResource } from 'core-app/features/hal/resources/project-phase-resource';
3435

3536
export class ProjectPhaseDisplayField extends DisplayField {
@@ -51,42 +52,40 @@ export class ProjectPhaseDisplayField extends DisplayField {
5152
* The icon is wrapped in a span element with the correct css class set for coloring
5253
* the icon in the color defined for the definition.
5354
*
55+
* @param phaseDefinitionId The ID of the phase definition (used for CSS class)
56+
* @param addPadding Whether to add right margin padding
5457
* @return {HTMLElement} The HTML span element containing the project phase icon.
5558
* @see phaseIcon
5659
*/
57-
public static phaseIconByName(phaseName?:string, addPadding = true) {
60+
public static phaseIconById(phaseDefinitionId?:string, addPadding = true) {
5861
const icon = document.createElement('span');
5962

60-
if (phaseName) {
63+
if (phaseDefinitionId) {
6164
if (addPadding) {
6265
icon.classList.add('mr-1');
6366
}
6467

65-
icon.setAttribute('data-test-selector', `project-phase-icon ${phaseName}`);
68+
icon.setAttribute('data-test-selector', `project-phase-icon phase-definition-${phaseDefinitionId}`);
6669

6770
icon.innerHTML = toDOMString(
6871
opPhaseIconData,
6972
'small',
7073
{ 'aria-hidden': 'true', class: 'octicon' },
7174
);
7275

73-
// Use a base64 encoded string of the project phase name to access the definition's color.
74-
// That way the frontend does not have to load the definitions to get the color.
75-
// The name is guaranteed to be unique.
76-
// The = signs at the end of the base64 string are replaced with _ to make it a valid class name.
77-
// This needs to be kept in sync with the ColorsHelper#project_phase_color_css method in the backend.
78-
icon.classList.add(`__hl_inline_project_phase_definition_${btoa(phaseName).replace(/=/g, '_')}`);
76+
// Use the phase definition ID for the CSS class.
77+
// This is more robust than using the name as it avoids issues with special characters.
78+
icon.classList.add(`__hl_inline_project_phase_definition_${phaseDefinitionId}`);
7979
}
8080

8181
return icon;
8282
}
8383

8484
/**
85-
* @see phaseIconByName
85+
* @see phaseIconById
8686
*/
8787
protected phaseIcon():HTMLElement {
88-
const projectPhase = this.attribute as ProjectPhaseResource;
89-
90-
return ProjectPhaseDisplayField.phaseIconByName(projectPhase?.name);
88+
const definition = this.resource.projectPhaseDefinition as HalResource;
89+
return ProjectPhaseDisplayField.phaseIconById(definition?.id ?? undefined);
9190
}
9291
}

spec/features/work_packages/table/project_phase_field_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,11 @@
227227

228228
it "includes the group icon in the group row header" do
229229
within("#wp-table-rowgroup-1") do
230-
expect(page).to have_test_selector("project-phase-icon #{other_project_phase.name}")
230+
expect(page).to have_test_selector("project-phase-icon phase-definition-#{other_project_phase.definition_id}")
231231
end
232232

233233
within("#wp-table-rowgroup-2") do
234-
expect(page).to have_test_selector("project-phase-icon #{project_phase.name}")
234+
expect(page).to have_test_selector("project-phase-icon phase-definition-#{project_phase.definition_id}")
235235
end
236236
end
237237
end

0 commit comments

Comments
 (0)