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
4 changes: 1 addition & 3 deletions projects/netgrif-components-core/src/lib/panel/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
export * from './abstract-panel.component';
export * from './case-panel/abstract-case-panel.component';
export * from './task-panel/abstract-task-panel.component';
export * from './task-panel-list/abstract-task-list.component';
export * from './task-panel-list/task-panel-list-pagination/abstract-task-list-pagination.component';
export * from './workflow-panel/abstract-workflow-panel.component';
export * from './abstract/tabbed-virtual-scroll.component';
export * from './immediate/abstract-immediate-filter-text.component';
Expand All @@ -12,7 +10,7 @@ export * from './panel-item/abstract-panel-item.component';
export * from './task-panel-single/abstract-single-task.component';

/* DATA */
export * from './task-panel-list/task-panel-data/task-panel-data';
export * from './task-panel-data/task-panel-data';
export * from './task-panel/models/disable-functions';
export * from './abstract/featured-value';
export * from './immediate/model/filter-text-injection-token';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Task} from '../../../resources/interface/task';
import {Task} from '../../resources/interface/task';
import {Subject} from 'rxjs';
import {ChangedFields} from '../../../data-fields/models/changed-fields';
import {ChangedFields} from '../../data-fields/models/changed-fields';


export interface TaskPanelData {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {AllowedNetsService} from '../../allowed-nets/services/allowed-nets.servi
import {AllowedNetsServiceFactory} from '../../allowed-nets/services/factory/allowed-nets-service-factory';
import {ActivatedRoute} from '@angular/router';
import { AbstractSingleTaskComponent } from './abstract-single-task.component';
import { TaskPanelData } from '../task-panel-list/task-panel-data/task-panel-data';
import { TaskPanelData } from '../task-panel-data/task-panel-data';


describe('AbstractSingleTaskComponent', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, EventEmitter, Inject, Input, OnDestroy, Optional, Output, TemplateRef } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { TaskPanelData } from '../task-panel-list/task-panel-data/task-panel-data';
import { TaskPanelData } from '../task-panel-data/task-panel-data';
import { MatExpansionPanel } from '@angular/material/expansion';
import { HeaderColumn } from '../../header/models/header-column';
import { TaskEventNotification } from '../../task-content/model/task-event-notification';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {SearchService} from '../../search/search-service/search.service';
import {TestTaskBaseFilterProvider, TestTaskViewAllowedNetsFactory} from '../../utility/tests/test-factory-methods';
import {ErrorSnackBarComponent} from '../../snack-bar/components/error-snack-bar/error-snack-bar.component';
import {SuccessSnackBarComponent} from '../../snack-bar/components/success-snack-bar/success-snack-bar.component';
import {TaskPanelData} from '../task-panel-list/task-panel-data/task-panel-data';
import {TaskPanelData} from '../task-panel-data/task-panel-data';
import {AssignPolicy, DataFocusPolicy, FinishPolicy} from '../../task-content/model/policy';
import {ChangedFields} from '../../data-fields/models/changed-fields';
import {HeaderColumn, HeaderColumnType} from '../../header/models/header-column';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {MatExpansionPanel} from '@angular/material/expansion';
import {ComponentPortal} from '@angular/cdk/portal';
import {TaskContentService} from '../../task-content/services/task-content.service';
import {LoggerService} from '../../logger/services/logger.service';
import {TaskPanelData} from '../task-panel-list/task-panel-data/task-panel-data';
import {TaskPanelData} from '../task-panel-data/task-panel-data';
import {Observable, Subscription} from 'rxjs';
import {TaskViewService} from '../../view/task-view/service/task-view.service';
import {filter, map, take} from 'rxjs/operators';
Expand Down Expand Up @@ -53,6 +53,7 @@ import { FinishPolicyService } from '../../task/services/finish-policy.service';
import {NAE_TAB_DATA} from '../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../tabs/interfaces';
import {AfterAction} from '../../utility/call-chain/after-action';
import {UnlimitedTaskContentService} from "../../task-content/services/unlimited-task-content.service";

@Component({
selector: 'ncc-abstract-legal-notice',
Expand Down Expand Up @@ -257,6 +258,17 @@ export abstract class AbstractTaskPanelComponent extends AbstractPanelWithImmedi
@Input()
public set taskPanelData(data: TaskPanelData) {
this._taskPanelData = data;
if (this._taskContentService instanceof UnlimitedTaskContentService && this.panelRef) {
this.collapse();
this._taskContentService.task = this._taskPanelData.task;
if (this._sub) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know it is not the code of the author, and this is only for future reference: attribute name _sub does not express what is the purpose of this attribute. I searched up, that this attribute is a subscription of changedFields, so something like _changedFieldSub would me better.

this._sub.unsubscribe();
}
this._sub = this._taskPanelData.changedFields.subscribe(chFields => {
this._taskContentService.updateFromChangedFields(chFields);
});
this.expand();
}
Comment on lines +261 to +271
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add documentation for the UnlimitedTaskContentService special handling.

The logic correctly handles dynamic task switching for UnlimitedTaskContentService by collapsing the panel, updating the task, properly managing subscriptions, and re-expanding. However, the purpose and rationale for this special handling should be documented.

Consider adding a JSDoc comment or inline comment explaining:

  • Why UnlimitedTaskContentService requires collapse/expand during task switches
  • Whether the collapse/expand sequence is necessary for cleaning up state or preventing UI issues
  • Any side effects or timing considerations developers should be aware of

Example:

     @Input()
     public set taskPanelData(data: TaskPanelData) {
         this._taskPanelData = data;
+        // UnlimitedTaskContentService allows dynamic task switching within the same panel.
+        // Collapse and re-expand to properly reset panel state and trigger lifecycle hooks.
         if (this._taskContentService instanceof UnlimitedTaskContentService && this.panelRef) {
             this.collapse();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this._taskContentService instanceof UnlimitedTaskContentService && this.panelRef) {
this.collapse();
this._taskContentService.task = this._taskPanelData.task;
if (this._sub) {
this._sub.unsubscribe();
}
this._sub = this._taskPanelData.changedFields.subscribe(chFields => {
this._taskContentService.updateFromChangedFields(chFields);
});
this.expand();
}
@Input()
public set taskPanelData(data: TaskPanelData) {
this._taskPanelData = data;
// UnlimitedTaskContentService allows dynamic task switching within the same panel.
// Collapse and re-expand to properly reset panel state and trigger lifecycle hooks.
if (this._taskContentService instanceof UnlimitedTaskContentService && this.panelRef) {
this.collapse();
this._taskContentService.task = this._taskPanelData.task;
if (this._sub) {
this._sub.unsubscribe();
}
this._sub = this._taskPanelData.changedFields.subscribe(chFields => {
this._taskContentService.updateFromChangedFields(chFields);
});
this.expand();
}
}
🤖 Prompt for AI Agents
In
projects/netgrif-components-core/src/lib/panel/task-panel/abstract-task-panel.component.ts
around lines 261 to 271, add a concise JSDoc or inline comment above the
UnlimitedTaskContentService special-case block explaining why collapse() and
expand() are required when switching tasks: state that
UnlimitedTaskContentService preserves internal UI/state tied to a task so
collapsing ensures teardown of UI/state and unsubscribes, updating the
service.task replaces internal references, re-subscribing to changedFields must
be done after teardown to avoid stale handlers, and note any timing or
side-effect considerations (e.g., animations or async cleanup) so future
maintainers understand the rationale and when it can be safely changed.

this.resolveFeaturedFieldsValues();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import {InjectionToken} from '@angular/core';
import {TaskContentServiceType} from './task-content-service-type';

export const NAE_TASK_CONTENT_SERVICE_TYPE = new InjectionToken<TaskContentServiceType>('NaeTaskContentServiceType');
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export enum TaskContentServiceType {
/**
* Type for creating SingleTaskContentService
*/
SINGLE = 'single',
/**
* Type for creating UnlimitedTaskContentService
*/
UNLIMITED = 'unlimited'
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './services/field-converter.service';
export * from './services/task-content.service';
export * from './services/single-task-content.service';
export * from './services/unlimited-task-content.service';
export * from './services/task-content-service-factory';

/* MODELS */
export * from './model/policy';
Expand All @@ -17,6 +18,8 @@ export * from './model/async-rendering-configuration-injection-token';
export * from './model/subgrid';
export * from './model/split-data-group';
export * from './model/task-fields';
export * from './model/task-content-service-type';
export * from './model/task-content-injection-token';

/* MODULES */
export * from './task-content/abstract-task-content.component';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Injectable, Injector} from "@angular/core";
import {LoggerService} from "../../logger/services/logger.service";
import {FieldConverterService} from "./field-converter.service";
import {SnackBarService} from "../../snack-bar/services/snack-bar.service";
import {TranslateService} from "@ngx-translate/core";
import {SingleTaskContentService} from "./single-task-content.service";
import {UnlimitedTaskContentService} from "./unlimited-task-content.service";

@Injectable({
providedIn: 'root'
})
export class TaskContentServiceFactory {

constructor(protected _fieldConverterService: FieldConverterService,
protected _snackBarService: SnackBarService,
protected _translate: TranslateService,
protected _log: LoggerService,
protected _injector: Injector) {
}

public createSingleTaskContentService(): SingleTaskContentService {
return new SingleTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
}

public createUnlimitedTaskContentService(): UnlimitedTaskContentService {
return new UnlimitedTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
}
Comment on lines +21 to +27
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding JSDoc for public factory methods.

The factory methods are well-implemented and follow Angular patterns. Consider adding JSDoc comments to document when each service type should be used:

+    /**
+     * Creates a SingleTaskContentService instance.
+     * Use for components that should display only one task at a time (e.g., task lists).
+     */
     public createSingleTaskContentService(): SingleTaskContentService {
         return new SingleTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
     }

+    /**
+     * Creates an UnlimitedTaskContentService instance.
+     * Use for components that can display multiple tasks simultaneously (e.g., single task views).
+     */
     public createUnlimitedTaskContentService(): UnlimitedTaskContentService {
         return new UnlimitedTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public createSingleTaskContentService(): SingleTaskContentService {
return new SingleTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
}
public createUnlimitedTaskContentService(): UnlimitedTaskContentService {
return new UnlimitedTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
}
/**
* Creates a SingleTaskContentService instance.
* Use for components that should display only one task at a time (e.g., task lists).
*/
public createSingleTaskContentService(): SingleTaskContentService {
return new SingleTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
}
/**
* Creates an UnlimitedTaskContentService instance.
* Use for components that can display multiple tasks simultaneously (e.g., single task views).
*/
public createUnlimitedTaskContentService(): UnlimitedTaskContentService {
return new UnlimitedTaskContentService(this._fieldConverterService, this._snackBarService, this._translate, this._log, this._injector);
}
🤖 Prompt for AI Agents
In
projects/netgrif-components-core/src/lib/task-content/services/task-content-service-factory.ts
around lines 21 to 27, add JSDoc comments above each public factory method
describing the purpose and intended usage: document
createSingleTaskContentService as returning a SingleTaskContentService for
single-task contexts (briefly noting expected behavior/constraints) and document
createUnlimitedTaskContentService as returning an UnlimitedTaskContentService
for multi- or unlimited-task contexts (briefly noting differences and when to
prefer it); keep comments concise, mention parameter-less methods and return
type, and follow existing project JSDoc/style conventions.

}
2 changes: 2 additions & 0 deletions projects/netgrif-components-core/src/lib/view/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ export * from './tree-case-view/tree-component/remove-node/abstract-remove-node.
export * from './tree-case-view/tree-task-content/abstract-tree-task-content.component';
export * from './task-view/abstract-single-task-view.component';
export * from './task-view/abstract-tabbed-single-task-view.component';
export * from './task-view/components/task-panel-list/abstract-task-list.component';
export * from './task-view/components/task-panel-list-pagination/abstract-task-list-pagination.component';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Component, EventEmitter, Input, Output} from '@angular/core';
import {AbstractViewWithHeadersComponent} from '../abstract/view-with-headers';
import {Observable} from 'rxjs';
import {TaskPanelData} from '../../panel/task-panel-list/task-panel-data/task-panel-data';
import {TaskPanelData} from '../../panel/task-panel-data/task-panel-data';
import {TaskViewService} from './service/task-view.service';
import {ActivatedRoute} from '@angular/router';
import {map, tap} from "rxjs/operators";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Observable} from 'rxjs';
import {Component, OnDestroy} from '@angular/core';
import {TaskPanelData} from '../../panel/task-panel-list/task-panel-data/task-panel-data';
import {TaskPanelData} from '../../panel/task-panel-data/task-panel-data';
import {TaskViewService} from './service/task-view.service';
import {AbstractViewWithHeadersComponent} from '../abstract/view-with-headers';
import { ActivatedRoute } from '@angular/router';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import {MatExpansionPanel} from '@angular/material/expansion';
import {ActivatedRoute} from '@angular/router';
import {filter} from 'rxjs/operators';
import {TabbedVirtualScrollComponent} from '../../abstract/tabbed-virtual-scroll.component';
import {TabbedVirtualScrollComponent} from '../../../../panel/abstract/tabbed-virtual-scroll.component';
import {AfterViewInit, Component, EventEmitter, Inject, Input, OnDestroy, Optional, Output} from '@angular/core';
import {Observable, Subject, Subscription} from 'rxjs';
import {TaskPanelData} from '../task-panel-data/task-panel-data';
import {HeaderColumn} from '../../../header/models/header-column';
import {TaskEventNotification} from '../../../task-content/model/task-event-notification';
import {TaskViewService} from '../../../view/task-view/service/task-view.service';
import {LoggerService} from '../../../logger/services/logger.service';
import {NAE_TAB_DATA} from '../../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../../tabs/interfaces';
import {TaskPanelData} from '../../../../panel/task-panel-data/task-panel-data';
import {HeaderColumn} from '../../../../header/models/header-column';
import {TaskEventNotification} from '../../../../task-content/model/task-event-notification';
import {TaskViewService} from '../../service/task-view.service';
import {LoggerService} from '../../../../logger/services/logger.service';
import {NAE_TAB_DATA} from '../../../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../../../tabs/interfaces';


@Component({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import {Observable} from 'rxjs';
import {tap} from 'rxjs/operators';
import {ActivatedRoute} from '@angular/router';
import {AbstractDefaultTaskListComponent} from '../default-task-panel-list/abstract-default-task-list.component';
import {TaskPanelData} from '../task-panel-data/task-panel-data';
import {TaskViewService} from '../../../view/task-view/service/task-view.service';
import {LoggerService} from '../../../logger/services/logger.service';
import {NAE_TAB_DATA} from '../../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../../tabs/interfaces';
import {TaskPanelData} from '../../../../panel/task-panel-data/task-panel-data';
import {TaskViewService} from '../../service/task-view.service';
import {LoggerService} from '../../../../logger/services/logger.service';
import {NAE_TAB_DATA} from '../../../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../../../tabs/interfaces';

@Component({
selector: 'ncc-abstract-task-list-pagination-component',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@ import {RouterTestingModule} from '@angular/router/testing';
import {Component, Inject, Optional} from '@angular/core';
import {of} from 'rxjs';
import {AbstractTaskListComponent} from './abstract-task-list.component';
import {AssignPolicy, DataFocusPolicy, FinishPolicy} from '../../task-content/model/policy';
import {TaskResourceService} from '../../resources/engine-endpoint/task-resource.service';
import {MaterialModule} from '../../material/material.module';
import {AuthenticationMethodService} from '../../authentication/services/authentication-method.service';
import {MockAuthenticationMethodService} from '../../utility/tests/mocks/mock-authentication-method-service';
import {AuthenticationService} from '../../authentication/services/authentication/authentication.service';
import {MockAuthenticationService} from '../../utility/tests/mocks/mock-authentication.service';
import {UserResourceService} from '../../resources/engine-endpoint/user-resource.service';
import {MockUserResourceService} from '../../utility/tests/mocks/mock-user-resource.service';
import {SearchService} from '../../search/search-service/search.service';
import {TestTaskBaseFilterProvider, TestTaskViewAllowedNetsFactory} from '../../utility/tests/test-factory-methods';
import {ConfigurationService} from '../../configuration/configuration.service';
import {TestConfigurationService} from '../../utility/tests/test-config';
import {TaskViewService} from '../../view/task-view/service/task-view.service';
import {LoggerService} from '../../logger/services/logger.service';
import {TranslateLibModule} from '../../translate/translate-lib.module';
import {NAE_TAB_DATA} from '../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../tabs/interfaces';
import {NAE_BASE_FILTER} from '../../search/models/base-filter-injection-token';
import {AllowedNetsService} from '../../allowed-nets/services/allowed-nets.service';
import {AllowedNetsServiceFactory} from '../../allowed-nets/services/factory/allowed-nets-service-factory';
import {AssignPolicy, DataFocusPolicy, FinishPolicy} from '../../../../task-content/model/policy';
import {TaskResourceService} from '../../../../resources/engine-endpoint/task-resource.service';
import {MaterialModule} from '../../../../material/material.module';
import {AuthenticationMethodService} from '../../../../authentication/services/authentication-method.service';
import {MockAuthenticationMethodService} from '../../../../utility/tests/mocks/mock-authentication-method-service';
import {AuthenticationService} from '../../../../authentication/services/authentication/authentication.service';
import {MockAuthenticationService} from '../../../../utility/tests/mocks/mock-authentication.service';
import {UserResourceService} from '../../../../resources/engine-endpoint/user-resource.service';
import {MockUserResourceService} from '../../../../utility/tests/mocks/mock-user-resource.service';
import {SearchService} from '../../../../search/search-service/search.service';
import {TestTaskBaseFilterProvider, TestTaskViewAllowedNetsFactory} from '../../../../utility/tests/test-factory-methods';
import {ConfigurationService} from '../../../../configuration/configuration.service';
import {TestConfigurationService} from '../../../../utility/tests/test-config';
import {TaskViewService} from '../../service/task-view.service';
import {LoggerService} from '../../../../logger/services/logger.service';
import {TranslateLibModule} from '../../../../translate/translate-lib.module';
import {NAE_TAB_DATA} from '../../../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../../../tabs/interfaces';
import {NAE_BASE_FILTER} from '../../../../search/models/base-filter-injection-token';
import {AllowedNetsService} from '../../../../allowed-nets/services/allowed-nets.service';
import {AllowedNetsServiceFactory} from '../../../../allowed-nets/services/factory/allowed-nets-service-factory';
import {ActivatedRoute} from '@angular/router';


Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {Component, Inject, Input, Optional, ViewChild} from '@angular/core';
import {CdkVirtualScrollViewport} from '@angular/cdk/scrolling';
import {TaskViewService} from '../../view/task-view/service/task-view.service';
import {LoggerService} from '../../logger/services/logger.service';
import {NAE_TAB_DATA} from '../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../tabs/interfaces';
import {TaskViewService} from '../../service/task-view.service';
import {LoggerService} from '../../../../logger/services/logger.service';
import {NAE_TAB_DATA} from '../../../../tabs/tab-data-injection-token/tab-data-injection-token';
import {InjectedTabData} from '../../../../tabs/interfaces';
import {ActivatedRoute} from '@angular/router';
import {AbstractDefaultTaskListComponent} from './default-task-panel-list/abstract-default-task-list.component';
import {AbstractDefaultTaskListComponent} from '../default-task-panel-list/abstract-default-task-list.component';
import {Observable} from 'rxjs';
import {TaskPanelData} from './task-panel-data/task-panel-data';
import {TaskPanelData} from '../../../../panel/task-panel-data/task-panel-data';

@Component({
selector: 'ncc-abstract-task-list',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {TaskPanelData} from '../../../panel/task-panel-list/task-panel-data/task-panel-data';
import {TaskPanelData} from '../../../panel/task-panel-data/task-panel-data';
import {PageLoadRequestContext} from '../../abstract/page-load-request-context';

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {createMockTask} from '../../../utility/tests/utility/create-mock-task';
import {ElementaryPredicate} from '../../../search/models/predicate/elementary-predicate';
import {Query} from '../../../search/models/query/query';
import {Page} from '../../../resources/interface/page';
import {TaskPanelData} from '../../../panel/task-panel-list/task-panel-data/task-panel-data';
import {TaskPanelData} from '../../../panel/task-panel-data/task-panel-data';
import {SnackBarModule} from '../../../snack-bar/snack-bar.module';
import {MockAuthenticationMethodService} from '../../../utility/tests/mocks/mock-authentication-method-service';
import {NAE_BASE_FILTER} from '../../../search/models/base-filter-injection-token';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {Inject, Injectable, OnDestroy, Optional} from '@angular/core';
import {BehaviorSubject, Observable, of, ReplaySubject, Subject, Subscription, timer} from 'rxjs';
import {TaskPanelData} from '../../../panel/task-panel-list/task-panel-data/task-panel-data';
import {TaskPanelData} from '../../../panel/task-panel-data/task-panel-data';
import {TaskResourceService} from '../../../resources/engine-endpoint/task-resource.service';
import {UserService} from '../../../user/services/user.service';
import {SnackBarService} from '../../../snack-bar/services/snack-bar.service';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,31 @@ import {
TaskRequestStateService,
TaskViewService,
ViewIdService,
TaskSearchRequestBody,
DataGroup,
extractFieldValueFromData,
SimpleFilter,
FilterType,
MergeOperator
} from '@netgrif/components-core';
import {AsyncPipe} from "@angular/common";
import {
InjectedTabbedTaskViewDataWithNavigationItemTaskData
} from "../../model/injected-tabbed-task-view-data-with-navigation-item-task-data";

function baseFilterFactory(injectedTabData: InjectedTabbedTaskViewDataWithNavigationItemTaskData) {
const requestBody = injectedTabData.baseFilter.getRequestBody() as TaskSearchRequestBody
if (requestBody.transitionId === undefined) {
Comment on lines +35 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard against null/undefined requestBody before property access.

The type assertion on line 35 doesn't guarantee that getRequestBody() returns a non-null value. If it returns null or undefined, line 36's property access will throw a TypeError at runtime.

Apply this diff to add a null check:

 function baseFilterFactory(injectedTabData: InjectedTabbedTaskViewDataWithNavigationItemTaskData) {
-    const requestBody = injectedTabData.baseFilter.getRequestBody() as TaskSearchRequestBody
-    if (requestBody.transitionId === undefined) {
+    const requestBody = injectedTabData.baseFilter.getRequestBody() as TaskSearchRequestBody;
+    if (requestBody && requestBody.transitionId === undefined) {
         const viewDataGroups: Array<DataGroup> = injectedTabData.navigationItemTaskData?.slice(4, injectedTabData.navigationItemTaskData.length);
🤖 Prompt for AI Agents
In
projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-single-task-view/default-tabbed-single-task-view.component.ts
around lines 35 to 36, the code asserts getRequestBody() as
TaskSearchRequestBody but then accesses requestBody.transitionId without
guarding against null/undefined; update the code to first retrieve the result
into a local variable, check if it is null or undefined and handle that case
(return early or set a safe default) before accessing transitionId, ensuring no
property access occurs on a nullish value.

const viewDataGroups: Array<DataGroup> = injectedTabData.navigationItemTaskData?.slice(4, injectedTabData.navigationItemTaskData.length);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Explain or extract the magic number 4 and simplify slice usage.

The hardcoded index 4 lacks context—why skip the first 4 elements? This makes maintenance difficult if the data structure changes.

Additionally, slice(4, array.length) is equivalent to slice(4).

Consider applying this diff:

+    // First 4 data groups contain [specific purpose - add comment explaining why], 
+    // remaining groups contain the transition_id field
-    const viewDataGroups: Array<DataGroup> = injectedTabData.navigationItemTaskData?.slice(4, injectedTabData.navigationItemTaskData.length);
+    const VIEW_DATA_START_INDEX = 4;
+    const viewDataGroups: Array<DataGroup> = injectedTabData.navigationItemTaskData?.slice(VIEW_DATA_START_INDEX);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const viewDataGroups: Array<DataGroup> = injectedTabData.navigationItemTaskData?.slice(4, injectedTabData.navigationItemTaskData.length);
// First 4 data groups contain [specific purpose – add comment explaining why],
// remaining groups contain the transition_id field
const VIEW_DATA_START_INDEX = 4;
const viewDataGroups: Array<DataGroup> =
injectedTabData.navigationItemTaskData?.slice(VIEW_DATA_START_INDEX);
🤖 Prompt for AI Agents
In
projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-single-task-view/default-tabbed-single-task-view.component.ts
around line 37, replace the magic number 4 and simplify the slice call:
introduce a clearly named constant (e.g., SKIP_INITIAL_GROUPS or
FIRST_DATA_GROUP_INDEX) or compute the start index with a descriptive helper so
the reason for skipping the first elements is explicit, change slice(4,
injectedTabData.navigationItemTaskData.length) to slice(startIndex) and add a
brief comment explaining why those initial elements are omitted.

if (viewDataGroups !== undefined) {
const viewTransitionId = extractFieldValueFromData<string>(viewDataGroups, "transition_id");
if (viewTransitionId !== undefined) {
return {
filter: injectedTabData.baseFilter.merge(new SimpleFilter('', FilterType.TASK, {transitionId: viewTransitionId?.split(",")}), MergeOperator.AND)
};
}
Comment on lines +39 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wrap field extraction in error handling to prevent runtime exceptions.

extractFieldValueFromData throws an error if the field "transition_id" doesn't exist (see relevant snippet). If this field is not guaranteed to be present in all navigation items, the factory will crash at runtime.

Apply this diff to handle missing fields gracefully:

         if (viewDataGroups !== undefined) {
-            const viewTransitionId = extractFieldValueFromData<string>(viewDataGroups, "transition_id");
-            if (viewTransitionId !== undefined) {
+            try {
+                const viewTransitionId = extractFieldValueFromData<string>(viewDataGroups, "transition_id");
+                if (viewTransitionId !== undefined && viewTransitionId !== '') {
+                    return {
+                        filter: injectedTabData.baseFilter.merge(new SimpleFilter('', FilterType.TASK, {transitionId: viewTransitionId.split(",")}), MergeOperator.AND)
+                    };
+                }
+            } catch (error) {
+                // Field not found, fallback to base filter
+            }
+        }
+    }
+    return {
+        filter: injectedTabData.baseFilter
+    };
+}

Note: Also removed the redundant optional chaining (viewTransitionId?.split) since we already verify it's defined.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const viewTransitionId = extractFieldValueFromData<string>(viewDataGroups, "transition_id");
if (viewTransitionId !== undefined) {
return {
filter: injectedTabData.baseFilter.merge(new SimpleFilter('', FilterType.TASK, {transitionId: viewTransitionId?.split(",")}), MergeOperator.AND)
};
}
if (viewDataGroups !== undefined) {
try {
const viewTransitionId = extractFieldValueFromData<string>(viewDataGroups, "transition_id");
if (viewTransitionId !== undefined && viewTransitionId !== '') {
return {
filter: injectedTabData.baseFilter.merge(new SimpleFilter('', FilterType.TASK, {transitionId: viewTransitionId.split(",")}), MergeOperator.AND)
};
}
} catch (error) {
// Field not found, fallback to base filter
}
}
}
}
return {
filter: injectedTabData.baseFilter
};
}
🤖 Prompt for AI Agents
projects/netgrif-components/src/lib/navigation/group-navigation-component-resolver/default-components/tabbed/default-tabbed-single-task-view/default-tabbed-single-task-view.component.ts
lines 39-44: extractFieldValueFromData can throw if "transition_id" is missing,
so wrap the extraction in a try/catch (or check existence first) and only
build/return the filter when extraction succeeds and yields a defined string; on
error or undefined, fall through (return undefined or the existing default) to
avoid crashing, and remove the redundant optional chaining on split since you
will only call split when the value is defined.

}
}
return {
filter: injectedTabData.baseFilter
};
Expand Down
Loading
Loading