Skip to content

Commit

Permalink
[52076] Add link from work sum to detailed view
Browse files Browse the repository at this point in the history
This link helps the user understand how the derived value of work or
remaining work is computed by showing the work package's children and
their values.
  • Loading branch information
cbliard committed Jan 11, 2024
1 parent 2726795 commit 50336a5
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,35 +40,42 @@ export class WorkPackageStateLinksHandler implements TableEventHandler {
protected workPackage:WorkPackageResource;

public handleEvent(view:TableEventComponent, evt:JQuery.TriggeredEvent) {
// Avoid the state capture when clicking with modifier
evt.stopPropagation();

// Avoid the state capture when clicking with modifier to allow browser opening in new tab
if (evt.shiftKey || evt.ctrlKey || evt.metaKey || evt.altKey) {
return true;
}

// Locate the details link from event
const target = jQuery(evt.target);
const element = target.closest(this.SELECTOR);
const state = element.data('wpState');
const workPackageId = element.data('workPackageId');
// debugger;
const target = evt.target as HTMLElement;
const element = target.closest(this.SELECTOR) as HTMLElement & { dataset:DOMStringMap };
const state = element.dataset.wpState;
const workPackageId = element.dataset.workPackageId;

// Normal link processing if there are no state and work package information
if (!state || !workPackageId) {
return true;
}

// Blur the target to avoid focus being kept there
target.closest('a').blur();
target.closest('a')?.blur();

// The current row is the last selected work package
// not matter what other rows are (de-)selected below.
// Thus save that row for the details view button.
// Locate the row from event
const row = target.closest(`.${tableRowClassName}`);
const classIdentifier = row.data('classIdentifier');
const [index, _] = view.workPackageTable.findRenderedRow(classIdentifier);
const row = target.closest(`.${tableRowClassName}`) as HTMLElement & { dataset:DOMStringMap };
const classIdentifier = row.dataset.classIdentifier as string;
const [index] = view.workPackageTable.findRenderedRow(classIdentifier);

// Update single selection if no modifier present
this.wpTableSelection.setSelection(workPackageId, index);

view.stateLinkClicked.emit({ workPackageId, requestedState: state });

evt.preventDefault();
evt.stopPropagation();
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export const cssClassCustomOption = 'custom-option';
export class DisplayField<T extends HalResource = HalResource> extends Field {
public static type:string;

public resource:T;

public mode:string | null = null;

public activeChange:ResourceChangeset<T>|null = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,20 @@

import { DisplayField } from 'core-app/shared/components/fields/display/display-field.module';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
import * as URI from 'urijs';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
import { ProjectResource } from 'core-app/features/hal/resources/project-resource';
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { uiStateLinkClass } from 'core-app/features/work-packages/components/wp-fast-table/builders/ui-state-link-builder';

export class EstimatedTimeDisplayField extends DisplayField {
@InjectField() timezoneService:TimezoneService;

@InjectField() PathHelper:PathHelperService;

@InjectField() apiV3Service:ApiV3Service;

private derivedText = this.I18n.t('js.label_value_derived_from_children');

public get valueString():string {
Expand Down Expand Up @@ -100,13 +109,15 @@ export class EstimatedTimeDisplayField extends DisplayField {
}

public renderDerived(element:HTMLElement, displayText:string):void {
const span = document.createElement('span');
const link = document.createElement('a');

span.textContent = ${displayText}`;
span.title = `${this.derivedValueString} ${this.derivedText}`;
span.classList.add('-derived-value');
link.textContent = ${displayText}`;
link.title = `${this.derivedValueString} ${this.derivedText}`;
link.classList.add('-derived-value', uiStateLinkClass);

element.appendChild(span);
this.addURLToViewWorkPackageChildren(link);

element.appendChild(link);
}

public get title():string|null {
Expand All @@ -120,4 +131,29 @@ export class EstimatedTimeDisplayField extends DisplayField {

return !value && !derived;
}

private addURLToViewWorkPackageChildren(link:HTMLAnchorElement):void {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (this.resource && this.resource.id && this.resource.project) {
const wpID = this.resource.id.toString();
this
.apiV3Service
.projects
.id(this.resource.project as ProjectResource)
.get()
.subscribe((project:ProjectResource) => {
const props = {
c: ['id', 'subject', 'type', 'status', 'estimatedTime', 'remainingTime'],
hi: true,
is: true,
f: [{ n: 'parent', o: '=', v: [wpID] }],
};
const href = URI(this.PathHelper.projectWorkPackagesPath(project.identifier as string))
.query({ query_props: JSON.stringify(props) })
.toString();

link.href = href;
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@ export class WorkPackageSpentTimeDisplayField extends EstimatedTimeDisplayField
link.setAttribute('class', 'time-logging--value');
}

if (this.resource.project) {
if (this.resource.project && this.resource.id) {
const wpID = this.resource.id.toString();
this
.apiV3Service
.projects
.id(this.resource.project)
.id(this.resource.project as ProjectResource)
.get()
.subscribe((project:ProjectResource) => {
// Link to the cost report having the work package filter preselected. No grouping.
const href = URI(this.PathHelper.projectTimeEntriesPath(project.identifier))
const href = URI(this.PathHelper.projectTimeEntriesPath(project.identifier as string))
.search(`fields[]=WorkPackageId&operators[WorkPackageId]=%3D&values[WorkPackageId]=${wpID}&set_filter=1`)
.toString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ display-field
padding-right: 0.25rem
text-align: center

.-derived-value
color: var(--color-fg-muted)
font-weight: var(--base-text-weight-bold)

&.spentTime .time-logging--value
padding: 0 2px

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,11 @@

let(:wp_table) { Pages::WorkPackagesTable.new project }
let(:editor) { Components::WysiwygEditor.new }
let(:initiator_work_package) { child }

before do
WorkPackages::UpdateAncestorsService
.new(user:, work_package: child)
.new(user:, work_package: initiator_work_package)
.call([:estimated_hours])

login_as(user)
Expand Down Expand Up @@ -142,4 +143,61 @@

include_examples 'estimated time display', expected_text: '-'
end

describe 'link to detailed view' do
let_work_packages(<<~TABLE)
hierarchy | work |
parent | 5h |
child 1 | 0h |
child 2 | 3h |
grand child 21 | 12h |
child 3 | |
other one | 2h |
TABLE

# Run UpdateAncestorsService on the grand child to update the whole hierarchy derived values
let(:initiator_work_package) { grand_child21 }

it 'displays a link to a detailed view explaining work calculation' do
wp_table.visit_query query

# parent
expect(page).to have_content("5 h·Σ 20 h")
expect(page).to have_link("Σ 20 h")
# child 2
expect(page).to have_content("3 h·Σ 15 h")
expect(page).to have_link("Σ 15 h")
end

context 'when clicking the link of a top parent' do
before do
visit work_package_path(parent)
end

it 'shows a work package table with a parent filter to list the direct children' do
click_on("Σ 20 h")

wp_table.expect_work_package_count(4)
wp_table.expect_work_package_listed(parent, child1, child2, child3)
within(:table) do
expect(page).to have_columnheader('Work')
expect(page).to have_columnheader('Remaining work')
end
end
end

context 'when clicking the link of an intermediate parent' do
before do
visit work_package_path(child2)
end

it 'shows also all ancestors in the work package table' do
expect(page).to have_content("Work\n3 h·Σ 15 h")
click_on("Σ 15 h")

wp_table.expect_work_package_count(3)
wp_table.expect_work_package_listed(parent, child2, grand_child21)
end
end
end
end

0 comments on commit 50336a5

Please sign in to comment.