From 80a8353565a4f991d6aae33e198268558951c7aa Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sat, 13 Jun 2026 18:45:55 +0100 Subject: [PATCH] [OP-19543] Replace lodash null/empty helpers Null/empty/type predicates move from the global `_` to native checks: `isNil` -> `== null`, `isNaN` -> `Number.isNaN`, `defaultTo` -> `??`, `compact` -> `filter(Boolean)`. `isEmpty`, `castArray`, `isObject` and `reject` are replaced per call site with the equivalent native form for the concrete collection type. The global `_` stays until the remaining buckets land. `compact` sites whose result feeds a non-nullable type use a narrowing `(x): x is NonNullable` predicate, since `filter(Boolean)` does not narrow in TypeScript. https://community.openproject.org/wp/OP-19543 --- frontend/src/app/core/apiv3/api-v3.service.ts | 2 +- .../src/app/core/apiv3/paths/apiv3-resource.ts | 2 +- .../src/app/core/apiv3/paths/path-resources.ts | 2 +- .../core/current-user/current-user.service.ts | 6 +++--- .../features/hal/helpers/hal-resource-builder.ts | 10 +++++----- .../app/features/hal/helpers/lazy-accessor.ts | 2 +- .../hal/services/hal-resource.service.ts | 4 +--- .../center/state/ian-center.service.ts | 2 +- ...filter-toggled-multiselect-value.component.ts | 2 +- .../wp-edit-form/work-package-filter-values.ts | 2 +- .../modes/grouped/grouped-rows-builder.ts | 2 +- .../components/wp-query/url-params-helper.ts | 2 +- .../wp-relation-inline-add-existing.component.ts | 2 +- .../relations/wp-relation-query.component.ts | 2 +- .../tabs/sort-by-tab.component.ts | 2 +- .../table-actions/table-actions.service.ts | 2 +- .../timeline/cells/timeline-cell-renderer.ts | 16 ++++++++-------- .../cells/timeline-milestone-cell-renderer.ts | 4 ++-- .../wp-table/timeline/cells/wp-timeline-cell.ts | 4 ++-- .../wp-timeline-relations.directive.ts | 4 ++-- .../components/wp-table/timeline/wp-timeline.ts | 4 ++-- .../view-services/wp-view-columns.service.ts | 2 +- .../view-services/wp-view-filters.service.ts | 2 +- .../wp-view-highlighting.service.ts | 2 +- .../view-services/wp-view-order.service.ts | 2 +- .../wp-view-relation-columns.service.ts | 4 ++-- .../view-services/wp-view-timeline.service.ts | 4 ++-- .../op-autocompleter.component.ts | 4 ++-- .../datepicker/helpers/date-modal.helpers.ts | 2 +- .../wp-date-picker-instance.component.ts | 4 ++-- .../fields/changeset/resource-changeset.ts | 2 +- .../resources-display-field.module.ts | 2 +- .../fields/edit/edit-form/edit-form.ts | 2 +- .../multi-select-edit-field.component.ts | 4 ++-- .../confirm-dialog/confirm-dialog.modal.ts | 16 ++++++++-------- .../project-life-cycle-form.controller.ts | 2 +- .../dynamic/sort-by-config.controller.ts | 7 +++---- .../date-picker/preview.controller.ts | 4 ++-- 38 files changed, 70 insertions(+), 73 deletions(-) diff --git a/frontend/src/app/core/apiv3/api-v3.service.ts b/frontend/src/app/core/apiv3/api-v3.service.ts index dc39e5742c3..e0ed1be5859 100644 --- a/frontend/src/app/core/apiv3/api-v3.service.ts +++ b/frontend/src/app/core/apiv3/api-v3.service.ts @@ -186,7 +186,7 @@ export class ApiV3Service { * @param projectIdentifier */ public withOptionalProject(projectIdentifier:string|number|null|undefined):ApiV3ProjectPaths|this { - if (_.isNil(projectIdentifier)) { + if (projectIdentifier == null) { return this; } return this.projects.id(projectIdentifier); diff --git a/frontend/src/app/core/apiv3/paths/apiv3-resource.ts b/frontend/src/app/core/apiv3/paths/apiv3-resource.ts index 85fa6150fbc..5481fae66ec 100644 --- a/frontend/src/app/core/apiv3/paths/apiv3-resource.ts +++ b/frontend/src/app/core/apiv3/paths/apiv3-resource.ts @@ -100,7 +100,7 @@ export class ApiV3ResourceCollection> exte } public withOptionalId(id?:string|number|null):this|T { - if (_.isNil(id)) { + if (id == null) { return this; } return this.id(id); diff --git a/frontend/src/app/core/apiv3/paths/path-resources.ts b/frontend/src/app/core/apiv3/paths/path-resources.ts index eda663d524d..e0665bb5039 100644 --- a/frontend/src/app/core/apiv3/paths/path-resources.ts +++ b/frontend/src/app/core/apiv3/paths/path-resources.ts @@ -26,7 +26,7 @@ export class SimpleResourceCollection { * @param id */ public withOptionalId(id?:string|number):this|T { - if (_.isNil(id)) { + if (id == null) { return this; } return this.id(id); diff --git a/frontend/src/app/core/current-user/current-user.service.ts b/frontend/src/app/core/current-user/current-user.service.ts index ccdad8f88dc..76e27a80227 100644 --- a/frontend/src/app/core/current-user/current-user.service.ts +++ b/frontend/src/app/core/current-user/current-user.service.ts @@ -80,7 +80,7 @@ export class CurrentUserService { .principalFilter$() .pipe( map((userFilter) => { - const filters:ApiV3ListFilter[] = _.compact([userFilter]); + const filters:ApiV3ListFilter[] = [userFilter].filter((x):x is NonNullable => Boolean(x)); if (projectContext) { filters.push(['context', '=', [projectContext === 'global' || projectContext === 'projects' ? 'g' : `w${projectContext}`]]); @@ -101,7 +101,7 @@ export class CurrentUserService { * in the provided context. */ public hasCapabilities$(action:string|string[], projectContext:string|null):Observable { - const actions = _.castArray(action); + const actions = Array.isArray(action) ? action : [action]; return this .capabilities$(actions, projectContext) .pipe( @@ -118,7 +118,7 @@ export class CurrentUserService { * has any of the required capabilities in the provided context. */ public hasAnyCapabilityOf$(actions:string|string[], projectContext:string|null):Observable { - const actionsToFilter = _.castArray(actions); + const actionsToFilter = Array.isArray(actions) ? actions : [actions]; return this .capabilities$(actionsToFilter, projectContext) .pipe( diff --git a/frontend/src/app/features/hal/helpers/hal-resource-builder.ts b/frontend/src/app/features/hal/helpers/hal-resource-builder.ts index 5b10acc3023..96622464e29 100644 --- a/frontend/src/app/features/hal/helpers/hal-resource-builder.ts +++ b/frontend/src/app/features/hal/helpers/hal-resource-builder.ts @@ -6,14 +6,14 @@ import { OpenprojectHalModuleHelpers } from 'core-app/features/hal/helpers/lazy- import { HalSource } from 'core-app/features/hal/interfaces'; export function cloneHalResourceCollection(values:T[]|undefined):T[] { - if (_.isNil(values)) { + if (values == null) { return []; } return values.map((v) => v.$copy()); } export function cloneHalResource(value:T|undefined):T|undefined { - if (_.isNil(value)) { + if (value == null) { return value; } return value.$copy(); @@ -38,7 +38,7 @@ export function initializeHalProperties(halResourceServic } function asHalResource(value?:HalSource, loaded = true):HalResource|HalSource|undefined|null { - if (_.isNil(value)) { + if (value == null) { return value; } @@ -122,7 +122,7 @@ export function initializeHalProperties(halResourceServic const sourceName = `_${name}`; const sourceObj:any = halResource.$source[sourceName]; - if (_.isObject(sourceObj)) { + if (typeof sourceObj === 'object' && sourceObj !== null) { Object.keys(sourceObj).forEach((propName) => { OpenprojectHalModuleHelpers.lazy((halResource)[instanceName], propName, @@ -147,7 +147,7 @@ export function initializeHalProperties(halResourceServic return element.map((source) => asHalResource(source, true)); } - if (_.isObject(element)) { + if (typeof element === 'object' && element !== null) { _.each(element, (child:any, name:string) => { if (child && (child._embedded || child._links)) { OpenprojectHalModuleHelpers.lazy(element as any, diff --git a/frontend/src/app/features/hal/helpers/lazy-accessor.ts b/frontend/src/app/features/hal/helpers/lazy-accessor.ts index 7cb77bd477d..077efc88e22 100644 --- a/frontend/src/app/features/hal/helpers/lazy-accessor.ts +++ b/frontend/src/app/features/hal/helpers/lazy-accessor.ts @@ -33,7 +33,7 @@ export namespace OpenprojectHalModuleHelpers { property:string, getter:() => any, setter?:(value:any) => void):void { - if (_.isObject(obj)) { + if (typeof obj === 'object' && obj !== null) { let done = false; let value:any; const config:any = { diff --git a/frontend/src/app/features/hal/services/hal-resource.service.ts b/frontend/src/app/features/hal/services/hal-resource.service.ts index 2d86b4f0eb1..05e4d9b2a7f 100644 --- a/frontend/src/app/features/hal/services/hal-resource.service.ts +++ b/frontend/src/app/features/hal/services/hal-resource.service.ts @@ -216,9 +216,7 @@ export class HalResourceService { * @returns {HalResource} */ public createHalResource(source:any, loaded = true):T { - if (_.isNil(source)) { - source = HalResource.getEmptyResource(); - } + source ??= HalResource.getEmptyResource(); const type = source._type || 'HalResource'; return this.createHalResourceOfType(type, source, loaded); diff --git a/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts b/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts index 3da1115fed6..a876156de0d 100644 --- a/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts +++ b/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts @@ -325,7 +325,7 @@ export class IanCenterService extends UntilDestroyedMixin { const promise = this .apiV3Service .work_packages - .requireAll(_.compact(wpIds)); + .requireAll(wpIds.filter(Boolean)); wpIds.forEach((id) => { cache.clearAndLoad( diff --git a/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts b/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts index 7626c72b92d..b1e850785ab 100644 --- a/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts +++ b/frontend/src/app/features/work-packages/components/filters/filter-toggled-multiselect-value/filter-toggled-multiselect-value.component.ts @@ -87,7 +87,7 @@ export class FilterToggledMultiselectValueComponent implements OnInit, AfterView } public setValues(val:HalResource[]|string[]|string|HalResource):void { - this.filter.values = _.castArray(val) as HalResource[]|string[]; + this.filter.values = (Array.isArray(val) ? val : [val]) as HalResource[]|string[]; this.filterChanged.emit(this.filter); this.cdRef.detectChanges(); } diff --git a/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.ts b/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.ts index bc08508d895..8507a82d4a5 100644 --- a/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.ts +++ b/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.ts @@ -140,7 +140,7 @@ export class WorkPackageFilterValues { */ private filterAlreadyApplied(change:WorkPackageChangeset|Record, filter:{ id:string, values:unknown[] }):boolean { const value:unknown = change instanceof WorkPackageChangeset ? change.projectedResource[filter.id] : change[filter.id]; - const current = _.castArray(value); + const current = Array.isArray(value) ? value : [value]; for (let i = 0; i < filter.values.length; i++) { for (let j = 0; j < current.length; j++) { diff --git a/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/grouped/grouped-rows-builder.ts b/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/grouped/grouped-rows-builder.ts index 84fa9a068e6..ffe9383738d 100644 --- a/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/grouped/grouped-rows-builder.ts +++ b/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/grouped/grouped-rows-builder.ts @@ -35,7 +35,7 @@ export class GroupedRowsBuilder extends RowsBuilder { * The hierarchy builder is only applicable if the hierarchy mode is active */ public isApplicable(table:WorkPackageTable) { - return !_.isEmpty(this.groups); + return this.groups.length > 0; } /** diff --git a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts index 82818ab51a9..1f88b3339ce 100644 --- a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts +++ b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts @@ -229,7 +229,7 @@ export class UrlParamsHelperService { if (query.timelineVisible) { paramsData.tv = query.timelineVisible; - if (!_.isEmpty(query.timelineLabels)) { + if (Object.keys(query.timelineLabels ?? {}).length > 0) { paramsData.tll = JSON.stringify(query.timelineLabels); } diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts index 3c641efd522..69fa03b9f67 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/inline/add-existing/wp-relation-inline-add-existing.component.ts @@ -82,7 +82,7 @@ export class WpRelationInlineAddExistingComponent { }; public addExisting() { - if (_.isNil(this.selectedWpId)) { + if (this.selectedWpId == null) { return; } diff --git a/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts index 65074c54228..1c0e4ad0949 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/embedded/relations/wp-relation-query.component.ts @@ -109,7 +109,7 @@ export class WorkPackageRelationQueryComponent extends WorkPackageRelationQueryB // When relations have changed, refresh this table this.wpRelations.observe(this.workPackage.id!) .pipe( - filter((val) => !_.isEmpty(val)), + filter((val) => !(val == null || (Array.isArray(val) ? val.length === 0 : Object.keys(val).length === 0))), this.untilDestroyed(), ) .subscribe(() => this.refreshTable()); diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts index 20416cbafbf..315cf53de6c 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/tabs/sort-by-tab.component.ts @@ -75,7 +75,7 @@ export class WpTableConfigurationSortByTabComponent implements TabComponent, OnI } sortElements = sortElements.map((object) => this.getMatchingSort(object.column.href!, object.direction)); - this.wpTableSortBy.update(_.compact(sortElements)); + this.wpTableSortBy.update(sortElements.filter((x):x is NonNullable => Boolean(x))); } ngOnInit() { diff --git a/frontend/src/app/features/work-packages/components/wp-table/table-actions/table-actions.service.ts b/frontend/src/app/features/work-packages/components/wp-table/table-actions/table-actions.service.ts index 9a2f1fa75a3..315296bff38 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/table-actions/table-actions.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/table-actions/table-actions.service.ts @@ -32,6 +32,6 @@ export class OpTableActionsService { */ public render(workPackage:WorkPackageResource):HTMLElement[] { const built = this.actions.map((factory) => factory(this.injector, workPackage).buildElement()); - return _.compact(built); + return built.filter((x):x is NonNullable => Boolean(x)); } } diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts index d799a7386d3..64689e4327d 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts @@ -85,7 +85,7 @@ export class TimelineCellRenderer { public isEmpty(wp:WorkPackageResource) { const start = moment(wp.startDate); const due = moment(wp.dueDate); - const noStartAndDueValues = _.isNaN(start.valueOf()) && _.isNaN(due.valueOf()); + const noStartAndDueValues = Number.isNaN(start.valueOf()) && Number.isNaN(due.valueOf()); return noStartAndDueValues; } @@ -246,21 +246,21 @@ export class TimelineCellRenderer { let start = moment(change.projectedResource.startDate); let due = moment(change.projectedResource.dueDate); - if (_.isNaN(start.valueOf()) && _.isNaN(due.valueOf())) { + if (Number.isNaN(start.valueOf()) && Number.isNaN(due.valueOf())) { element.style.visibility = 'hidden'; } else { element.style.visibility = 'visible'; } // only start date, fade out bar to the right - if (_.isNaN(due.valueOf()) && !_.isNaN(start.valueOf())) { + if (Number.isNaN(due.valueOf()) && !Number.isNaN(start.valueOf())) { // Set due date to today due = moment(); bar.setAttribute('style', 'background-image: linear-gradient(90deg, rgba(255,255,255,0) 0%, #F1F1F1 100%) !important'); } // only finish date, fade out bar to the left - if (_.isNaN(start.valueOf()) && !_.isNaN(due.valueOf())) { + if (Number.isNaN(start.valueOf()) && !Number.isNaN(due.valueOf())) { start = due.clone(); bar.setAttribute('style', 'background-image: linear-gradient(90deg, #F1F1F1 0%, rgba(255,255,255,0) 80%) !important'); } @@ -336,7 +336,7 @@ export class TimelineCellRenderer { let start = moment(projection.startDate); const due = moment(projection.dueDate); - start = _.isNaN(start.valueOf()) ? due.clone() : start; + start = Number.isNaN(start.valueOf()) ? due.clone() : start; const offsetStart = start.diff(renderInfo.viewParams.dateDisplayStart, 'days'); @@ -349,8 +349,8 @@ export class TimelineCellRenderer { let start = moment(projection.startDate); let due = moment(projection.dueDate); - start = _.isNaN(start.valueOf()) ? due.clone() : start; - due = _.isNaN(due.valueOf()) ? start.clone() : due; + start = Number.isNaN(start.valueOf()) ? due.clone() : start; + due = Number.isNaN(due.valueOf()) ? start.clone() : due; const offsetStart = start.diff(renderInfo.viewParams.dateDisplayStart, 'days'); const duration = due.diff(start, 'days') + 1; @@ -461,7 +461,7 @@ export class TimelineCellRenderer { element.style.width = calculatePositionValueForDayCount(viewParams, duration); // ensure minimum width - if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) { + if (!Number.isNaN(start.valueOf()) || !Number.isNaN(due.valueOf())) { const minWidth = _.max([renderInfo.viewParams.pixelPerDay, 2]); element.style.minWidth = `${minWidth}px`; } diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts index c7a087bef4b..151955e0d1e 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts @@ -31,7 +31,7 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer { public isEmpty(wp:WorkPackageResource) { const date = moment(wp.date); - return _.isNaN(date.valueOf()); + return Number.isNaN(date.valueOf()); } public canMoveDates(wp:WorkPackageResource) { @@ -120,7 +120,7 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer { const date = moment(renderInfo.change.projectedResource.date); // abort if no date - if (_.isNaN(date.valueOf())) { + if (Number.isNaN(date.valueOf())) { return false; } diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cell.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cell.ts index 899f911b045..fd8099d56c9 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cell.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cell.ts @@ -92,10 +92,10 @@ export class WorkPackageTimelineCell { canConnectRelations():boolean { const wp = this.latestRenderInfo.workPackage; if (this.schemaCache.of(wp).isMilestone) { - return !_.isNil(wp.date); + return wp.date != null; } - return !_.isNil(wp.startDate) || !_.isNil(wp.dueDate); + return wp.startDate != null || wp.dueDate != null; } public clear() { diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts index c9d055e3701..cf274911277 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts @@ -130,7 +130,7 @@ export class WorkPackageTableTimelineRelations extends UntilDestroyedMixin imple ) .subscribe((list) => { // ... make sure that the corresponding relations are loaded ... - const wps = _.compact(list.map((row) => row.workPackageId)); + const wps = list.map((row) => row.workPackageId).filter((x):x is NonNullable => Boolean(x)); void this.wpRelations.requireAll(wps); }); @@ -161,7 +161,7 @@ export class WorkPackageTableTimelineRelations extends UntilDestroyedMixin imple private renderWorkPackagesRelations(workPackageIds:string[]) { workPackageIds.forEach((workPackageId) => { const workPackageWithRelation = this.workPackagesWithRelations[workPackageId]; - if (_.isNil(workPackageWithRelation)) { + if (workPackageWithRelation == null) { return; } diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/wp-timeline.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/wp-timeline.ts index 56dca4c857d..f06e2600a38 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/wp-timeline.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/wp-timeline.ts @@ -164,9 +164,9 @@ export function getTimeSlicesForHeader(vp:TimelineViewParameters, const firstRest:[Moment, Moment] = rest.splice(0, 1)[0]; const lastRest:[Moment, Moment] = rest.pop()!; const inViewportAndBoundaries = _.concat( - [firstRest].filter((e) => !_.isNil(e)), + [firstRest].filter((e) => e != null), inViewport, - [lastRest].filter((e) => !_.isNil(e)), + [lastRest].filter((e) => e != null), ); return { diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-columns.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-columns.service.ts index aa165a42cba..87696106a23 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-columns.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-columns.service.ts @@ -175,7 +175,7 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService< public setColumnsById(columnIds:string[]) { const mapped = columnIds.map((id) => _.find(this.all, (c) => c.id === id)); - this.setColumns(_.compact(mapped)); + this.setColumns(mapped.filter((x):x is NonNullable => Boolean(x))); } /** diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service.ts index 17e3ec620e5..85a62902ce3 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service.ts @@ -283,7 +283,7 @@ export class WorkPackageViewFiltersService extends WorkPackageQueryStateService< const invisibleFilters = new Set(this.hidden); invisibleFilters.delete('search'); - return _.reject(this.current, (filter) => invisibleFilters.has(filter.id)); + return this.current.filter((filter) => !invisibleFilters.has(filter.id)); } /** diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts index beca9656068..971186c7ae7 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts @@ -76,7 +76,7 @@ export class WorkPackageViewHighlightingService extends WorkPackageQueryStateSer } private filteredValue(value:WorkPackageViewHighlight):WorkPackageViewHighlight { - if (_.isEmpty(value.selectedAttributes)) { + if (!value.selectedAttributes?.length) { value.selectedAttributes = undefined; } diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.ts index 8a8b2ece757..37301914b05 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-order.service.ts @@ -166,7 +166,7 @@ export class WorkPackageViewOrderService extends WorkPackageQueryStateService (_.isObject(el) ? el[this.inputBindValue as 'id'] : el) as string); + const mappedValues = this.model.map((el) => ((typeof el === 'object' && el !== null) ? el[this.inputBindValue as 'id'] : el) as string); return mappedValues.length > 0 ? mappedValues : ['']; } else { return this.model[this.inputBindValue as 'id'] as string || ''; @@ -562,7 +562,7 @@ export class OpAutocompleterComponent boolean) { return (a, b) => { - if (this.bindValue && !_.isObject(b)) { + if (this.bindValue && !(typeof b === 'object' && b !== null)) { return (a as Record)[this.bindValue] === b; } diff --git a/frontend/src/app/shared/components/datepicker/helpers/date-modal.helpers.ts b/frontend/src/app/shared/components/datepicker/helpers/date-modal.helpers.ts index a68319f320e..ba291cfbc06 100644 --- a/frontend/src/app/shared/components/datepicker/helpers/date-modal.helpers.ts +++ b/frontend/src/app/shared/components/datepicker/helpers/date-modal.helpers.ts @@ -87,7 +87,7 @@ export function comparableDate(date?:DateOption):number|null { export function setDates(dates:DateOption|DateOption[], datePicker:DatePicker, enforceDate?:Date):void { const { currentMonth, currentYear, selectedDates } = datePicker.datepickerInstance; - const [newStart, newEnd] = _.castArray(dates); + const [newStart, newEnd] = Array.isArray(dates) ? dates : [dates]; const [selectedStart, selectedEnd] = selectedDates; // In case the new times match the current times, do not try to update diff --git a/frontend/src/app/shared/components/datepicker/wp-date-picker-modal/wp-date-picker-instance.component.ts b/frontend/src/app/shared/components/datepicker/wp-date-picker-modal/wp-date-picker-instance.component.ts index 3f764fc6e5a..5ff514525ad 100644 --- a/frontend/src/app/shared/components/datepicker/wp-date-picker-modal/wp-date-picker-instance.component.ts +++ b/frontend/src/app/shared/components/datepicker/wp-date-picker-modal/wp-date-picker-instance.component.ts @@ -206,7 +206,7 @@ export class OpWpDatePickerInstanceComponent extends UntilDestroyedMixin impleme } private currentDates():string[] { - const compactedDates = _.compact([this.startDateValue, this.dueDateValue]); + const compactedDates = [this.startDateValue, this.dueDateValue].filter((x):x is NonNullable => Boolean(x)); return this.timezoneService.utcDatesToISODateStrings(compactedDates); } @@ -246,7 +246,7 @@ export class OpWpDatePickerInstanceComponent extends UntilDestroyedMixin impleme minDate: this.minDate, } as flatpickr.Options.Options; - return _.omitBy(options, (v) => _.isNil(v)); + return _.omitBy(options, (v) => v == null); } private onFlatpickrChange(dates:Date[], _datestr:string, _instance:flatpickr.Instance) { diff --git a/frontend/src/app/shared/components/fields/changeset/resource-changeset.ts b/frontend/src/app/shared/components/fields/changeset/resource-changeset.ts index 7d87022fda4..ef7b72c1f48 100644 --- a/frontend/src/app/shared/components/fields/changeset/resource-changeset.ts +++ b/frontend/src/app/shared/components/fields/changeset/resource-changeset.ts @@ -459,7 +459,7 @@ export class ResourceChangeset { protected getLinkedValue(val:any, fieldSchema:IFieldSchema) { // Links should always be nullified as { href: null }, but // this wasn't always the case, so ensure null values are returned as such. - if (_.isNil(val)) { + if (val == null) { return { href: null }; } diff --git a/frontend/src/app/shared/components/fields/display/field-types/resources-display-field.module.ts b/frontend/src/app/shared/components/fields/display/field-types/resources-display-field.module.ts index 43428211b39..8bd1bf0b9e1 100644 --- a/frontend/src/app/shared/components/fields/display/field-types/resources-display-field.module.ts +++ b/frontend/src/app/shared/components/fields/display/field-types/resources-display-field.module.ts @@ -30,7 +30,7 @@ import { cssClassCustomOption, DisplayField } from 'core-app/shared/components/f export class ResourcesDisplayField extends DisplayField { public isEmpty():boolean { - return _.isEmpty(this.value); + return this.value == null || (Array.isArray(this.value) ? this.value.length === 0 : Object.keys(this.value as Record).length === 0); } public get stringValue():string[] { diff --git a/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.ts b/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.ts index 9688885d8ff..9fd45b76879 100644 --- a/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.ts +++ b/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.ts @@ -101,7 +101,7 @@ export abstract class EditForm { * Return whether this form has any active fields */ public hasActiveFields():boolean { - return !_.isEmpty(this.activeFields); + return Object.keys(this.activeFields).length > 0; } /** diff --git a/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts b/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts index 1e435e3da5e..fbbcd12b6ee 100644 --- a/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts +++ b/frontend/src/app/shared/components/fields/edit/field-types/multi-select-edit-field.component.ts @@ -110,7 +110,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements */ public buildSelectedOption() { const value:HalResource[] = this.resource[this.name]; - return value ? _.castArray(value).map((val) => this.findValueOption(val)) : []; + return value ? (Array.isArray(value) ? value : [value]).map((val) => this.findValueOption(val)) : []; } public get selectedOption() { @@ -135,7 +135,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements return option; }; - this.resource[this.name] = _.castArray(val).map((el) => mapper(el)); + (this.resource as Record)[this.name] = (Array.isArray(val) ? val : [val]).map((el) => mapper(el) as ValueOption); } public onOpen() { diff --git a/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.ts b/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.ts index 23f59d5c132..86a59bfde1b 100644 --- a/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.ts +++ b/frontend/src/app/shared/components/modals/confirm-dialog/confirm-dialog.modal.ts @@ -100,14 +100,14 @@ export class ConfirmDialogModalComponent extends OpModalComponent { this.options = (this.locals.options ?? {}) as ConfirmDialogOptions; - this.dangerHighlighting = _.defaultTo(this.options.dangerHighlighting, false); - this.showListData = _.defaultTo(this.options.showListData, false); - this.refreshOnCancel = _.defaultTo(this.options.refreshOnCancel, false); - this.listTitle = _.defaultTo(this.options.listTitle, ''); - this.warningText = _.defaultTo(this.options.warningText, ''); - this.passedData = _.defaultTo(this.options.passedData, []); - this.showClose = _.defaultTo(this.options.showClose, true); - this.divideContent = _.defaultTo(this.options.divideContent, false); + this.dangerHighlighting = (this.options.dangerHighlighting ?? false); + this.showListData = (this.options.showListData ?? false); + this.refreshOnCancel = (this.options.refreshOnCancel ?? false); + this.listTitle = (this.options.listTitle ?? ''); + this.warningText = (this.options.warningText ?? ''); + this.passedData = (this.options.passedData ?? []); + this.showClose = (this.options.showClose ?? true); + this.divideContent = (this.options.divideContent ?? false); // override default texts and icons if any this.text = _.defaults(this.options.text, this.text); this.icon = _.defaults(this.options.icon, this.icon); diff --git a/frontend/src/stimulus/controllers/dynamic/overview/project-life-cycle-form.controller.ts b/frontend/src/stimulus/controllers/dynamic/overview/project-life-cycle-form.controller.ts index 16552dc061d..6da380c4d3f 100644 --- a/frontend/src/stimulus/controllers/dynamic/overview/project-life-cycle-form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/overview/project-life-cycle-form.controller.ts @@ -91,7 +91,7 @@ export default class ProjectLifeCycleFormController extends FormPreviewControlle } private updateFlatpickrCalendar() { - const dates:Date[] = _.compact(this.dateInputFields.map((field) => this.toDate(field.value))); + const dates:Date[] = this.dateInputFields.map((field) => this.toDate(field.value)).filter((x):x is NonNullable => Boolean(x)); const ignoreNonWorkingDays = false; const mode = 'range'; diff --git a/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts b/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts index 97d0a9967fe..87b8960b377 100644 --- a/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts @@ -29,7 +29,6 @@ */ import { Controller } from '@hotwired/stimulus'; -import { compact } from 'lodash'; export default class SortByConfigController extends Controller { static targets = [ @@ -74,7 +73,7 @@ export default class SortByConfigController extends Controller { return null; }); - return JSON.stringify(compact(filters)); + return JSON.stringify(filters.filter(Boolean)); } // Tries to find the parent form in the DOM. If present and the form contains a `page` field marked @@ -270,12 +269,12 @@ export default class SortByConfigController extends Controller { } getAllSelectedFields(...excludedRows:HTMLElement[]):string[] { - return compact(this.inputRowTargets.map((row) => { + return this.inputRowTargets.map((row) => { if (!excludedRows.includes(row)) { return this.getSelectedField(row); } return null; - })); + }).filter((x):x is NonNullable => Boolean(x)); } moveRowToBottom(row:HTMLElement):void { diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts index 176de654298..e54f711d870 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts @@ -250,7 +250,7 @@ export default class PreviewController extends DialogPreviewController { } private updateFlatpickrCalendar() { - const dates:Date[] = _.compact([this.currentStartDate, this.currentDueDate]); + const dates:Date[] = [this.currentStartDate, this.currentDueDate].filter((x):x is NonNullable => Boolean(x)); const ignoreNonWorkingDays = this.currentIgnoreNonWorkingDays; const mode = this.mode(); @@ -271,7 +271,7 @@ export default class PreviewController extends DialogPreviewController { return this.toDate(flatPickrDates[0]); } - const fieldDates = _.compact([this.currentStartDate, this.currentDueDate]) + const fieldDates = [this.currentStartDate, this.currentDueDate].filter((x):x is NonNullable => Boolean(x)) .map((date) => this.timezoneService.utcDateToISODateString(date)); const diff = _.difference(flatPickrDates, fieldDates); return this.toDate(diff[0]);