diff --git a/frontend/src/app/core/apiv3/endpoints/relations/apiv3-relations-paths.ts b/frontend/src/app/core/apiv3/endpoints/relations/apiv3-relations-paths.ts index fb05bee4717..4ac93eafcc6 100644 --- a/frontend/src/app/core/apiv3/endpoints/relations/apiv3-relations-paths.ts +++ b/frontend/src/app/core/apiv3/endpoints/relations/apiv3-relations-paths.ts @@ -56,7 +56,7 @@ export class ApiV3RelationsPaths extends ApiV3ResourceCollection this.loadInvolved(chunk))) .pipe( - map((results) => _.flatten(results)), + map((results) => results.flat()), ); } diff --git a/frontend/src/app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths.ts b/frontend/src/app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths.ts index 54a90bba3a6..6a82fe6dc54 100644 --- a/frontend/src/app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths.ts +++ b/frontend/src/app/core/apiv3/endpoints/work_packages/api-v3-work-packages-paths.ts @@ -70,7 +70,7 @@ export class ApiV3WorkPackagesPaths extends ApiV3Collection((resolve, reject) => { this - .loadCollectionsFor(_.uniq(ids)) + .loadCollectionsFor(Array.from(new Set(ids))) .then((pagedResults:WorkPackageCollectionResource[]) => { _.each(pagedResults, (results) => { if (results.schemas) { diff --git a/frontend/src/app/features/hal/resources/error-resource.ts b/frontend/src/app/features/hal/resources/error-resource.ts index c271408a68e..8f277a19f6e 100644 --- a/frontend/src/app/features/hal/resources/error-resource.ts +++ b/frontend/src/app/features/hal/resources/error-resource.ts @@ -94,20 +94,20 @@ export class ErrorResource extends HalResource { } public getInvolvedAttributes():string[] { - let columns = []; + let columns:ErrorResource[] = []; if (this.details) { - columns = [{ details: this.details }]; + columns = [{ details: this.details } as ErrorResource]; } else if (this.errors) { - columns = this.errors; + columns = this.errors as ErrorResource[]; } - return _.flatten(columns.map((resource:ErrorResource) => { + return columns.map((resource:ErrorResource):string => { if (resource.errorIdentifier === v3ErrorIdentifierMultipleErrors) { return this.extractMultiError(resource)[0]; } - return resource.details.attribute; - })); + return resource.details.attribute as string; + }).flat(); } public getMessagesPerAttribute():Record { diff --git a/frontend/src/app/features/hal/resources/hal-resource.ts b/frontend/src/app/features/hal/resources/hal-resource.ts index f9ac74aa795..0a927821ffc 100644 --- a/frontend/src/app/features/hal/resources/hal-resource.ts +++ b/frontend/src/app/features/hal/resources/hal-resource.ts @@ -286,7 +286,7 @@ export class HalResource { */ public $embeddableKeys():string[] { const properties = Object.keys(this.$source); - return _.without(properties, '_links', '_embedded', 'id'); + return properties.filter((property) => !['_links', '_embedded', 'id'].includes(property)); } /** @@ -295,6 +295,6 @@ export class HalResource { */ public $linkableKeys():string[] { const properties = Object.keys(this.$links); - return _.without(properties, 'self'); + return properties.filter((property) => property !== 'self'); } } diff --git a/frontend/src/app/features/hal/resources/project-resource.ts b/frontend/src/app/features/hal/resources/project-resource.ts index 10cb6240431..b9942e8922b 100644 --- a/frontend/src/app/features/hal/resources/project-resource.ts +++ b/frontend/src/app/features/hal/resources/project-resource.ts @@ -51,6 +51,6 @@ export class ProjectResource extends HalResource { * Exclude the schema _link from the linkable Resources. */ public $linkableKeys():string[] { - return _.without(super.$linkableKeys(), 'schema'); + return super.$linkableKeys().filter((key) => key !== 'schema'); } } diff --git a/frontend/src/app/features/hal/resources/time-entry-resource.ts b/frontend/src/app/features/hal/resources/time-entry-resource.ts index c391aef1e66..472960ee30e 100644 --- a/frontend/src/app/features/hal/resources/time-entry-resource.ts +++ b/frontend/src/app/features/hal/resources/time-entry-resource.ts @@ -56,7 +56,7 @@ export class TimeEntryResource extends HalResource { * Exclude the schema _link from the linkable Resources. */ public $linkableKeys():string[] { - return _.without(super.$linkableKeys(), 'schema'); + return super.$linkableKeys().filter((key) => key !== 'schema'); } } diff --git a/frontend/src/app/features/hal/resources/work-package-resource.ts b/frontend/src/app/features/hal/resources/work-package-resource.ts index 560aeb84ff2..6238cedaeda 100644 --- a/frontend/src/app/features/hal/resources/work-package-resource.ts +++ b/frontend/src/app/features/hal/resources/work-package-resource.ts @@ -290,7 +290,7 @@ export class WorkPackageBaseResource extends HalResource { * Exclude the schema _link from the linkable Resources. */ public $linkableKeys():string[] { - return _.without(super.$linkableKeys(), 'schema'); + return super.$linkableKeys().filter((key) => key !== 'schema'); } /** diff --git a/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts b/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts index df7f859d9f1..9c195079f13 100644 --- a/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/actors-line/in-app-notification-actors-line.component.ts @@ -69,6 +69,6 @@ export class InAppNotificationActorsLineComponent implements OnInit { }) .filter((actor) => actor !== null) as PrincipalLike[]; - this.actors = _.uniqBy(actors, (item) => item.href); + this.actors = actors.filter((item, index, self) => index === self.findIndex((other) => other.href === item.href)); } } diff --git a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts index 632622edd27..5493383924f 100644 --- a/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-card-view/services/wp-card-drag-and-drop.service.ts @@ -133,7 +133,7 @@ export class WorkPackageCardDragAndDropService { * Update current order */ private updateOrder(newOrder:string[]) { - newOrder = _.uniq(newOrder); + newOrder = Array.from(new Set(newOrder)); Promise .all(newOrder.map((id) => this diff --git a/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts b/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts index b0e0bf7d351..74fbc3b8f18 100644 --- a/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts +++ b/frontend/src/app/features/work-packages/components/wp-fast-table/builders/modes/hierarchy/hierarchy-render-pass.ts @@ -132,7 +132,12 @@ export class HierarchyRenderPass extends PrimaryRenderPass { // Append all new elements elements = elements.concat(newElements); // Remove duplicates (Regression #29652) - this.deferred[parent.id!] = _.uniqBy(elements, (el) => el.id!); + const seen = new Set(); + this.deferred[parent.id!] = elements.filter((el) => { + if (seen.has(el.id!)) { return false; } + seen.add(el.id!); + return true; + }); return true; } // Otherwise, continue the chain upwards diff --git a/frontend/src/app/features/work-packages/components/wp-fast-table/builders/relations/child-relations-render-pass.ts b/frontend/src/app/features/work-packages/components/wp-fast-table/builders/relations/child-relations-render-pass.ts index 4376ec1f180..3be1cf62c6a 100644 --- a/frontend/src/app/features/work-packages/components/wp-fast-table/builders/relations/child-relations-render-pass.ts +++ b/frontend/src/app/features/work-packages/components/wp-fast-table/builders/relations/child-relations-render-pass.ts @@ -72,7 +72,7 @@ export class ChildRelationsRenderPass extends RelationsRenderPass { } private loadMissingTargets(ids:string[]) { - const uniqueIds = _.uniq(ids); + const uniqueIds = Array.from(new Set(ids)); if (uniqueIds.length === 0 || this.loadingMissingTargets) { return; diff --git a/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts b/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts index 3c396d86b42..82311a2117e 100644 --- a/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts +++ b/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/state/drag-and-drop-transformer.ts @@ -168,7 +168,7 @@ export class DragAndDropTransformer { * Update current rendered order */ private async updateRenderedOrder(order:string[]) { - order = _.uniq(order); + order = Array.from(new Set(order)); const mappedOrder = await Promise.all( order.map( diff --git a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts index 21748e04203..48a8e645fe5 100644 --- a/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-single-view-tabs/activity-panel/wp-activity.service.ts @@ -69,7 +69,7 @@ export class WorkPackagesActivityService extends WorkPackageLinkedResourceCache< } protected sortedActivityList(activities:HalResource[], attr = 'createdAt'):HalResource[] { - const sorted = _.sortBy(_.flatten(activities), attr); + const sorted = _.sortBy(activities.flat(), attr); if (this.isReversed) { return sorted.reverse(); 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..f70cb68b078 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 @@ -90,7 +90,7 @@ export class WpTableConfigurationSortByTabComponent implements TabComponent, OnI // For whatever reason, even though the UI doesn't implement it, // QuerySortByResources are doubled for each column (one for asc/desc direction) - this.allColumns = _.uniqBy(allColumns, 'href'); + this.allColumns = allColumns.filter((col, index, self) => index === self.findIndex((other) => other.href === col.href)); this.getManualSortingOption(); @@ -122,7 +122,7 @@ export class WpTableConfigurationSortByTabComponent implements TabComponent, OnI .filter((o) => o.column !== null) .map((object:SortModalObject) => object.column); - this.availableColumns = _.sortBy(_.differenceBy(this.allColumns, usedColumns, 'href'), 'name'); + this.availableColumns = _.sortBy(this.allColumns.filter((col) => !usedColumns.some((used) => used.href === col.href)), 'name'); } public updateSortingMode(mode:SortingMode) { diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cells-renderer.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cells-renderer.ts index 29ec48095e0..db4c208271d 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cells-renderer.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/wp-timeline-cells-renderer.ts @@ -120,7 +120,7 @@ export class WorkPackageTimelineCellsRenderer { newCells.push(identifier); }); - _.difference(currentlyActive, newCells).forEach((identifier:string) => { + currentlyActive.filter((identifier) => !newCells.includes(identifier)).forEach((identifier:string) => { this.cells[identifier].clear(); delete this.cells[identifier]; }); diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-additional-elements.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-additional-elements.service.ts index 477c775fbe7..6a1cf7198ee 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-additional-elements.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-additional-elements.service.ts @@ -73,7 +73,7 @@ export class WorkPackageViewAdditionalElementsService { this.requireWorkPackageShares(workPackageIds), this.requireSumsSchema(results), ]).then((wpResults:string[][]) => { - this.loadAdditional(_.flatten(wpResults)); + this.loadAdditional(wpResults.flat()); }); } @@ -103,7 +103,7 @@ export class WorkPackageViewAdditionalElementsService { .requireAll(rows) .then(() => { const ids = this.getInvolvedWorkPackages(rows.map((id) => this.wpRelations.state(id).value!)); - return _.flatten(ids); + return ids.flat(); }); } @@ -116,9 +116,7 @@ export class WorkPackageViewAdditionalElementsService { return Promise.resolve([]); } - const ids = _.flatten( - rows.map((el) => el.children?.map((child) => child.id!) || []), - ); + const ids = rows.map((el) => el.children?.map((child) => child.id!) || []).flat(); return Promise.resolve(ids); } @@ -134,7 +132,7 @@ export class WorkPackageViewAdditionalElementsService { } const resultIds = rows.map((el:WorkPackageResource) => (el.id as string | number).toString()); - const ids = _.flatten(rows.map((el) => el.ancestorIds)) + const ids = rows.map((el) => el.ancestorIds).flat() .filter((id) => !resultIds.includes(id)); return Promise.resolve(ids); @@ -184,7 +182,7 @@ export class WorkPackageViewAdditionalElementsService { map((elements) => { const shares = elements as ShareResource[]; - const sharedWpIds = _.uniq(shares.map((share) => share.entity.id!)); + const sharedWpIds = Array.from(new Set(shares.map((share) => share.entity.id!))); sharedWpIds.forEach((wpId) => { this 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..c631d2ae263 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 @@ -65,7 +65,7 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService< query.columns = cloneHalResourceCollection(toApply); // We can avoid reloading even with relation columns if we only removed columns - const onlyRemoved = _.difference(newColumns, oldColumns).length === 0; + const onlyRemoved = newColumns.filter((column) => !oldColumns.includes(column)).length === 0; // Reload the table visibly if adding relation or share columns. return !onlyRemoved && (this.hasRelationColumns() || this.hasShareColumn()); @@ -283,7 +283,8 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService< * Get columns not yet selected */ public get unused():QueryColumn[] { - return _.differenceBy(this.all, this.getColumns(), '$href'); + const columns = this.getColumns(); + return this.all.filter((column) => !columns.some((other) => other.$href === column.$href)); } /** 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..3aa1d7abbe5 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 @@ -313,7 +313,7 @@ export class WorkPackageViewFiltersService extends WorkPackageQueryStateService< * Get all filters that are not in the current active set */ private remainingFilters(filters = this.rawFilters) { - return _.differenceBy(this.availableFilters, filters, (filter) => filter.id); + return this.availableFilters.filter((available) => !filters.some((filter) => filter.id === available.id)); } isAvailable(el:QueryFilterInstanceResource):boolean { diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-hierarchy-indentation.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-hierarchy-indentation.service.ts index 9ce2a774418..514863c6b98 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-hierarchy-indentation.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-hierarchy-indentation.service.ts @@ -91,7 +91,7 @@ export class WorkPackageViewHierarchyIdentationService { // get the first element of the ancestor chain that workPackage is not in const predecessor = await firstValueFrom(this.apiV3Service.work_packages.id(predecessorId).get()); - const difference = _.difference(predecessor.ancestorIds, workPackage.ancestorIds); + const difference = predecessor.ancestorIds.filter((id) => !workPackage.ancestorIds.includes(id)); if (difference && difference.length > 0) { newParentId = difference[0]; } diff --git a/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts b/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts index f2cf894165c..9c3979ba736 100644 --- a/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts +++ b/frontend/src/app/shared/components/autocompleter/user-autocompleter/user-autocompleter.component.ts @@ -127,7 +127,15 @@ export class UserAutocompleterComponent extends OpAutocompleterComponent>(filteredURL.toString()) .pipe( - map((res) => _.uniqBy(res._embedded.elements, (el) => el._links.self?.href || el.id)), + map((res) => { + const seen = new Set(); + return res._embedded.elements.filter((el) => { + const key = el._links.self?.href || el.id; + if (seen.has(key)) { return false; } + seen.add(key); + return true; + }); + }), map((users) => { const mapped:IUserAutocompleteItem[] = users.map((user) => { return { id: user.id, name: user.name, href: user._links.self?.href, email: user.email }; diff --git a/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts b/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts index f66cd491fad..ec27b02fd4f 100644 --- a/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts +++ b/frontend/src/app/shared/components/editor/components/ckeditor-augmented-textarea/ckeditor-augmented-textarea.component.ts @@ -307,11 +307,8 @@ export class CkeditorAugmentedTextareaComponent extends UntilDestroyedMixin impl filter((resource) => !!resource), ) .subscribe((resource:HalResource&{ attachments:AttachmentCollectionResource }) => { - const missingAttachments = _.differenceBy( - this.attachments, - resource.attachments.elements, - (attachment:HalResource) => attachment.id, - ); + const presentIds = new Set(resource.attachments.elements.map((other:HalResource) => other.id)); + const missingAttachments = this.attachments.filter((attachment:HalResource) => !presentIds.has(attachment.id)); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-return const removedUrls = missingAttachments.map((attachment) => attachment.downloadLocation.href); diff --git a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts index 3a8a8528d34..fb38cdf045d 100644 --- a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts +++ b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts @@ -327,7 +327,14 @@ export class SearchableProjectListService { return forkJoin(extraFetches).pipe( map((collections) => collections.map((collection) => collection._embedded.elements)), map((collections) => projects.concat(...collections)), - map((allProjects) => _.uniqBy(allProjects, (p) => p.id)), + map((allProjects) => { + const seen = new Set(); + return allProjects.filter((p) => { + if (seen.has(p.id)) { return false; } + seen.add(p.id); + return true; + }); + }), ); } diff --git a/frontend/src/app/shared/components/work-package-graphs/embedded/wp-embedded-graph.component.ts b/frontend/src/app/shared/components/work-package-graphs/embedded/wp-embedded-graph.component.ts index 9bfbba6d846..1399c35721d 100644 --- a/frontend/src/app/shared/components/work-package-graphs/embedded/wp-embedded-graph.component.ts +++ b/frontend/src/app/shared/components/work-package-graphs/embedded/wp-embedded-graph.component.ts @@ -75,10 +75,10 @@ export class WorkPackageEmbeddedGraphComponent implements OnChanges { } private updateChartData() { - let uniqLabels = _.uniq(this.datasets.reduce((array, dataset) => { + let uniqLabels = Array.from(new Set(this.datasets.reduce((array, dataset) => { const groups = (dataset.groups || []).map((group) => group.value) as any; return array.concat(groups); - }, [])) as string[]; + }, []))) as string[]; const labelCountMaps = this.datasets.map((dataset) => { const countMap = (dataset.groups || []).reduce((hash, group) => ({ diff --git a/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts b/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts index 80dd29e81e7..879ff0ca1ee 100644 --- a/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/filter/filters-form.controller.ts @@ -563,11 +563,11 @@ export default class FiltersFormController extends Controller { if (operator && this.daysOperators.includes(operator)) { const dateValue = this.findTargetByName(filterName, this.daysTargets)?.value; - value = _.without([dateValue], ''); + value = [dateValue].filter((v) => v !== ''); } else if (operator === this.onDateOperator) { const dateValue = this.findTargetById(filterName, this.singleDayTargets)?.value; - value = _.without([dateValue], ''); + value = [dateValue].filter((v) => v !== ''); } else if (operator === this.betweenDatesOperator) { const rangeValue = this.findTargetById(filterName, this.dateRangeTargets)?.value; const [fromValue, toValue] = rangeValue?.split(' - ') ?? []; 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..690fff01020 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 @@ -273,7 +273,7 @@ export default class PreviewController extends DialogPreviewController { const fieldDates = _.compact([this.currentStartDate, this.currentDueDate]) .map((date) => this.timezoneService.utcDateToISODateString(date)); - const diff = _.difference(flatPickrDates, fieldDates); + const diff = flatPickrDates.filter((date) => !fieldDates.includes(date)); return this.toDate(diff[0]); }