mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
[OP-19542] Migrate lodash iteration to native ES6
Iteration and collection helpers move from the global `_` to native Array and Object methods. Object-keyed collections become `Object.entries`/`Object.values` so lodash's value/key iteration is preserved, and a handful of nullable receivers gain the explicit guards that `_` applied implicitly. The global `_` stays until the remaining buckets land. Adds a vitest spec for `ApiV3FilterBuilder` exercising the migrated object iteration. https://community.openproject.org/wp/OP-19542
This commit is contained in:
@@ -142,7 +142,7 @@ export class StateCacheService<T> {
|
||||
auditTime(250),
|
||||
map(() => {
|
||||
const mapped:T[] = [];
|
||||
_.each(this.multiState.getValueOr({}), (state:State<T>) => {
|
||||
Object.values(this.multiState.getValueOr({})).forEach((state:State<T>) => {
|
||||
if (state.value) {
|
||||
mapped.push(state.value);
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ export class ApiV3RelationsPaths extends ApiV3ResourceCollection<RelationResourc
|
||||
);
|
||||
}
|
||||
|
||||
const validIds = _.filter(workPackageIds, (id) => /\d+/.test(id));
|
||||
const validIds = workPackageIds.filter((id) => /\d+/.test(id));
|
||||
|
||||
if (validIds.length === 0) {
|
||||
return from([]);
|
||||
|
||||
@@ -72,9 +72,9 @@ export class ApiV3WorkPackagesPaths extends ApiV3Collection<WorkPackageResource,
|
||||
this
|
||||
.loadCollectionsFor(_.uniq(ids))
|
||||
.then((pagedResults:WorkPackageCollectionResource[]) => {
|
||||
_.each(pagedResults, (results) => {
|
||||
pagedResults.forEach((results) => {
|
||||
if (results.schemas) {
|
||||
_.each(results.schemas.elements, (schema:SchemaResource) => {
|
||||
results.schemas.elements.forEach((schema:SchemaResource) => {
|
||||
this.states.schemas.get(schema.href!).putValue(schema);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -229,7 +229,7 @@ export function initializeUiRouterListeners(injector:Injector) {
|
||||
const projectIdentifier = toParams.projectPath as string || currentProject.identifier;
|
||||
if (hasProjectRoutes && !toParams.projects && projectIdentifier) {
|
||||
const newParams = _.clone(toParams);
|
||||
_.assign(newParams, { projectPath: projectIdentifier, projects: 'projects' });
|
||||
Object.assign(newParams, { projectPath: projectIdentifier, projects: 'projects' });
|
||||
return $state.target(toState, newParams, { location: 'replace' });
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export class BoardActionsRegistryService {
|
||||
}
|
||||
|
||||
public available():ITileViewEntry[] {
|
||||
return _.map(this.mapping, (service:BoardActionService, attribute:string) => ({
|
||||
return Object.entries(this.mapping).map(([attribute, service]) => ({
|
||||
attribute,
|
||||
text: service.localizedName,
|
||||
icon: '',
|
||||
|
||||
+1
-1
@@ -239,7 +239,7 @@ export class BoardListContainerComponent extends UntilDestroyedMixin implements
|
||||
const { filterName } = service;
|
||||
const idFilterName = `${filterName}_id`;
|
||||
const options = widget.options as unknown as BoardWidgetOption;
|
||||
const instance = _.find(options.filters, (f) => !!f[filterName] || !!f[idFilterName]);
|
||||
const instance = options.filters.find((f) => !!f[filterName] || !!f[idFilterName]);
|
||||
|
||||
if (instance) {
|
||||
return ((instance[filterName] || instance[idFilterName])?.values[0] || null) as unknown as string|null;
|
||||
|
||||
@@ -103,7 +103,7 @@ export class HalLink implements HalLinkInterface {
|
||||
}
|
||||
|
||||
let href = _.clone(this.href) || '';
|
||||
_.each(templateValues, (value:string, key:string) => {
|
||||
Object.entries(templateValues).forEach(([key, value]) => {
|
||||
const regexp = new RegExp(`{${key}}`);
|
||||
href = href.replace(regexp, value);
|
||||
});
|
||||
@@ -129,7 +129,7 @@ export class HalLink implements HalLinkInterface {
|
||||
public $callable():CallableHalLink {
|
||||
const linkFunc:any = (...params:any[]) => this.$fetch(...params);
|
||||
|
||||
_.extend(linkFunc, {
|
||||
Object.assign(linkFunc, {
|
||||
$link: this,
|
||||
href: this.href,
|
||||
title: this.title,
|
||||
|
||||
@@ -148,7 +148,7 @@ export function initializeHalProperties<T extends HalResource>(halResourceServic
|
||||
}
|
||||
|
||||
if (_.isObject(element)) {
|
||||
_.each(element, (child:any, name:string) => {
|
||||
Object.entries(element as Record<string, HalSource>).forEach(([name, child]) => {
|
||||
if (child && (child._embedded || child._links)) {
|
||||
OpenprojectHalModuleHelpers.lazy(element as any,
|
||||
name,
|
||||
|
||||
@@ -116,7 +116,7 @@ export class ErrorResource extends HalResource {
|
||||
if (this.details) {
|
||||
perAttribute[this.details.attribute] = [this.message];
|
||||
} else {
|
||||
_.forEach(this.errors, (error:any) => {
|
||||
this.errors?.forEach((error:ErrorResource) => {
|
||||
if (error.errorIdentifier === v3ErrorIdentifierMultipleErrors) {
|
||||
const [attribute, messages] = this.extractMultiError(error);
|
||||
const current = perAttribute[attribute] || [];
|
||||
|
||||
@@ -45,7 +45,7 @@ export class FormResource<T = HalResource> extends HalResource {
|
||||
public validationErrors:Record<string, ErrorResource>;
|
||||
|
||||
public getErrors():ErrorResource|null {
|
||||
const errors = _.values(this.validationErrors);
|
||||
const errors = Object.values(this.validationErrors);
|
||||
const count = errors.length;
|
||||
|
||||
if (count === 0) {
|
||||
|
||||
@@ -94,7 +94,7 @@ export class QueryFilterInstanceResource extends HalResource {
|
||||
}
|
||||
|
||||
public findOperator(operatorSymbol:string):QueryOperatorResource|undefined {
|
||||
return _.find(this.schemaCache.of(this).availableOperators, (operator:QueryOperatorResource) => operator.id === operatorSymbol) as QueryOperatorResource|undefined;
|
||||
return (this.schemaCache.of(this).availableOperators as QueryOperatorResource[]).find((operator:QueryOperatorResource) => operator.id === operatorSymbol);
|
||||
}
|
||||
|
||||
public isTemplated() {
|
||||
|
||||
@@ -118,7 +118,8 @@ export class QueryFilterInstanceSchemaResource extends SchemaResource {
|
||||
}
|
||||
|
||||
private definesAllowedValues() {
|
||||
return _.some(this._dependencies[0].dependencies,
|
||||
(dependency:any) => dependency.values?._links?.allowedValues);
|
||||
interface FilterDependency { values?:{ _links?:{ allowedValues?:unknown } } }
|
||||
const dependencies = (this._dependencies as { dependencies:FilterDependency[] }[])[0].dependencies;
|
||||
return dependencies.some((dependency:FilterDependency) => !!dependency.values?._links?.allowedValues);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ export class RelationResource extends HalResource {
|
||||
* @return {boolean}
|
||||
*/
|
||||
public isInvolved(wpId:string) {
|
||||
return _.values(this.ids).includes(wpId.toString());
|
||||
return Object.values(this.ids).includes(wpId.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
//++
|
||||
|
||||
import { HalResource } from 'core-app/features/hal/resources/hal-resource';
|
||||
import { HalSource } from 'core-app/features/hal/interfaces';
|
||||
import { InputState } from '@openproject/reactivestates';
|
||||
|
||||
export class SchemaResource extends HalResource {
|
||||
@@ -35,7 +36,7 @@ export class SchemaResource extends HalResource {
|
||||
}
|
||||
|
||||
public get availableAttributes():string[] {
|
||||
return _.keys(this.$source).filter((name) => !name.startsWith('_'));
|
||||
return Object.keys(this.$source as HalSource).filter((name) => !name.startsWith('_'));
|
||||
}
|
||||
|
||||
// Find the attribute name with a matching (localized) name;
|
||||
|
||||
@@ -266,7 +266,7 @@ describe('WorkPackage', () => {
|
||||
});
|
||||
|
||||
it('when the work package has an `addAttachment` link', () => {
|
||||
workPackage.$links.addAttachment = _.noop as any;
|
||||
workPackage.$links.addAttachment = () => Promise.resolve();
|
||||
|
||||
expect(workPackage.canAddAttachments).toEqual(true);
|
||||
});
|
||||
|
||||
@@ -264,7 +264,7 @@ export class WorkPackageBaseResource extends HalResource {
|
||||
resources[name] = linked ? linked.$update() : Promise.reject(undefined);
|
||||
});
|
||||
|
||||
const promise = Promise.all(_.values(resources));
|
||||
const promise = Promise.all(Object.values(resources));
|
||||
promise.then(() => {
|
||||
this.wpCacheService.touch(this.id!);
|
||||
});
|
||||
|
||||
@@ -56,7 +56,7 @@ export class HalPayloadHelper {
|
||||
* @param schema The associated schema to determine writable state of attributes
|
||||
*/
|
||||
static extractPayloadFromSchema<T extends HalResource = HalResource>(resource:T, schema:SchemaResource) {
|
||||
const payload:any = {
|
||||
const payload:{ _links:Record<string, unknown> } & Record<string, unknown> = {
|
||||
_links: {},
|
||||
};
|
||||
|
||||
@@ -66,7 +66,7 @@ export class HalPayloadHelper {
|
||||
if (schema.hasOwnProperty(key) && schema[key]?.writable) {
|
||||
if (resource.$links[key]) {
|
||||
if (Array.isArray(resource[key])) {
|
||||
payload._links[key] = _.map(resource[key], (element) => ({ href: (element as HalResource).href }));
|
||||
payload._links[key] = (resource[key] as HalResource[]).map((element) => ({ href: element.href }));
|
||||
} else {
|
||||
payload._links[key] = {
|
||||
href: (resource[key]?.href),
|
||||
@@ -78,10 +78,10 @@ export class HalPayloadHelper {
|
||||
}
|
||||
}
|
||||
|
||||
_.each(nonLinkProperties, (property) => {
|
||||
nonLinkProperties.forEach((property) => {
|
||||
if (resource.hasOwnProperty(property) || resource[property]) {
|
||||
if (Array.isArray(resource[property])) {
|
||||
payload[property] = _.map(resource[property], (element:any) => {
|
||||
payload[property] = (resource[property] as HalResource[]).map((element) => {
|
||||
if (element instanceof HalResource) {
|
||||
return this.extractPayloadFromSchema(element, element.currentSchema || element.schema);
|
||||
}
|
||||
|
||||
@@ -162,8 +162,9 @@ export class HalResourceNotificationService {
|
||||
|
||||
public showGeneralError(message?:unknown) {
|
||||
let error = this.I18n.t('js.error.internal');
|
||||
const hasOwnToString = message != null && Object.prototype.hasOwnProperty.call(message, 'toString');
|
||||
|
||||
if (typeof (message) === 'string' || _.has(message, 'toString')) {
|
||||
if (typeof (message) === 'string' || hasOwnToString) {
|
||||
error += ` ${(message as any).toString()}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -222,6 +222,6 @@ const halResourceDefaultConfig:Record<string, HalResourceFactoryConfigInterface>
|
||||
|
||||
export function initializeHalResourceConfig(halResourceService:HalResourceService) {
|
||||
return () => {
|
||||
_.each(halResourceDefaultConfig, (value, key) => halResourceService.registerResource(key, value));
|
||||
Object.entries(halResourceDefaultConfig).forEach(([key, value]) => halResourceService.registerResource(key, value));
|
||||
};
|
||||
}
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@ export class QueryFiltersComponent extends UntilDestroyedMixin implements OnInit
|
||||
}
|
||||
|
||||
public get isSecondSpacerVisible():boolean {
|
||||
const hasSearch = !!_.find(this.filters, (f) => f.id === 'search');
|
||||
const hasSearch = !!this.filters.find((f) => f.id === 'search');
|
||||
const hasAvailableFilter = !!this.filters.find((f) => f.id !== 'search' && this.isFilterAvailable(f));
|
||||
|
||||
return hasSearch && hasAvailableFilter;
|
||||
|
||||
+1
-1
@@ -226,7 +226,7 @@ export class OpBaselineComponent extends UntilDestroyedMixin implements OnInit {
|
||||
}
|
||||
|
||||
public dateChange(values:string[]):void {
|
||||
if (_.every(values, validDate)) {
|
||||
if (values.every(validDate)) {
|
||||
this.selectedDates = values;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -12,12 +12,12 @@ export class WorkPackageCardViewService {
|
||||
return `wp-row-${wp.id}`;
|
||||
}
|
||||
|
||||
public get renderedCards() {
|
||||
public get renderedCards():RenderedWorkPackage[] {
|
||||
return this.querySpace.tableRendered.getValueOr([]);
|
||||
}
|
||||
|
||||
public findRenderedCard(classIdentifier:string):number {
|
||||
const index = _.findIndex(this.renderedCards, (card) => card.classIdentifier === classIdentifier);
|
||||
const index = this.renderedCards.findIndex((card) => card.classIdentifier === classIdentifier);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ export class TableEditForm extends EditForm<WorkPackageResource> {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
_.each(this.activeFields, (field) => {
|
||||
Object.values(this.activeFields).forEach((field) => {
|
||||
field.deactivate(false);
|
||||
});
|
||||
this.resourceSubscription.unsubscribe();
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ export class WorkPackageFilterValues {
|
||||
) {}
|
||||
|
||||
applyDefaultsFromFilters(change:WorkPackageChangeset|Record<string, unknown>):void {
|
||||
_.each(this.filters, (filter) => {
|
||||
this.filters.forEach((filter) => {
|
||||
// Exclude filters specified in constructor
|
||||
if (this.excluded.includes(filter.id)) {
|
||||
return;
|
||||
@@ -42,7 +42,7 @@ export class WorkPackageFilterValues {
|
||||
if (operator !== '=') return;
|
||||
|
||||
const currentProjectId = this.currentProject.id;
|
||||
const projectFilter = _.find(filter.values, (resource:HalResource|string) => {
|
||||
const projectFilter = filter.values.find((resource:HalResource|string) => {
|
||||
const href = (resource instanceof HalResource) ? resource.href : resource;
|
||||
const hrefParts = href?.split('/');
|
||||
return hrefParts?.[hrefParts.length - 1] === currentProjectId;
|
||||
|
||||
+4
-4
@@ -70,7 +70,7 @@ export class GroupedRenderPass extends PlainRenderPass {
|
||||
* The API sadly doesn't provide us with the information which group a WP belongs to.
|
||||
*/
|
||||
private matchingGroup(workPackage:WorkPackageResource) {
|
||||
return _.find(this.groups, (group:GroupObject) => {
|
||||
return this.groups.find((group:GroupObject) => {
|
||||
let property = workPackage[groupByProperty(group)];
|
||||
// explicitly check for undefined as `false` (bool) is a valid value.
|
||||
if (property === undefined) {
|
||||
@@ -79,14 +79,14 @@ export class GroupedRenderPass extends PlainRenderPass {
|
||||
|
||||
// If the property is a multi-value
|
||||
// Compare the href's of all resources with the ones in valueLink
|
||||
if (_.isArray(property)) {
|
||||
if (Array.isArray(property)) {
|
||||
return this.matchesMultiValue(property as HalResource[], group);
|
||||
}
|
||||
|
||||
/// / If it's a linked resource, compare the href,
|
||||
/// / which is an array of links the resource offers
|
||||
if (property?.href) {
|
||||
return !!_.find(group._links.valueLink, (l:any):any => property.href === l.href);
|
||||
return !!group._links.valueLink.find((l) => (property as HalResource).href === l.href);
|
||||
}
|
||||
|
||||
// Otherwise, fall back to simple value comparison.
|
||||
@@ -108,7 +108,7 @@ export class GroupedRenderPass extends PlainRenderPass {
|
||||
return false;
|
||||
}
|
||||
|
||||
const joinedOrderedHrefs = (objects:any[]) => _.map(objects, (object) => object.href).sort().join(', ');
|
||||
const joinedOrderedHrefs = (objects:{ href:string }[]) => objects.map((object) => object.href).sort().join(', ');
|
||||
|
||||
return _.isEqualWith(
|
||||
property,
|
||||
|
||||
+2
-2
@@ -51,7 +51,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
|
||||
|
||||
this.hierarchies = this.wpTableHierarchies.current;
|
||||
|
||||
_.each(this.workPackageTable.originalRowIndex, (row) => {
|
||||
Object.values(this.workPackageTable.originalRowIndex).forEach((row) => {
|
||||
row.object.getAncestors().forEach((ancestor:WorkPackageResource) => {
|
||||
this.parentsWithVisibleChildren[ancestor.id!] = true;
|
||||
});
|
||||
@@ -208,7 +208,7 @@ export class HierarchyRenderPass extends PrimaryRenderPass {
|
||||
});
|
||||
|
||||
// Insert this row to parent
|
||||
const parent = _.last(ancestors);
|
||||
const parent = ancestors.at(-1);
|
||||
this.insertUnderParent(row, parent!);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ export class SingleHierarchyRowBuilder extends SingleRowBuilder {
|
||||
}
|
||||
|
||||
const ancestors = workPackage.getAncestors();
|
||||
if (_.isArray(ancestors)) {
|
||||
if (Array.isArray(ancestors)) {
|
||||
ancestors.forEach((ancestor) => {
|
||||
rowClasses.push(hierarchyGroupClass(ancestor.id!));
|
||||
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ export class RelationsRenderPass {
|
||||
// If the work package has no relations, ignore
|
||||
const { workPackage } = row;
|
||||
const state = this.wpRelations.state(workPackage.id!);
|
||||
if (!state.hasValue() || _.size(state.value) === 0) {
|
||||
if (!state.hasValue() || Object.keys(state.value ?? {}).length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ export class HierarchyTransformer {
|
||||
const collapsed:Record<number, boolean> = {};
|
||||
|
||||
// Hide all collapsed hierarchies
|
||||
_.each(state.collapsed, (isCollapsed:boolean, wpId:string) => {
|
||||
Object.entries(state.collapsed).forEach(([wpId, isCollapsed]) => {
|
||||
// Toggle the root style
|
||||
document.querySelector(`.${hierarchyRootClass(wpId)} .wp-table--hierarchy-indicator`)?.classList.toggle(indicatorCollapsedClass, isCollapsed);
|
||||
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ export class SelectionTransformer {
|
||||
|
||||
context.querySelectorAll(`.${tableRowClassName}.${checkedClassName}`).forEach((el) => el.classList.remove(checkedClassName));
|
||||
|
||||
_.each(state.selected, (selected:boolean, workPackageId:any) => {
|
||||
Object.entries(state.selected).forEach(([workPackageId, selected]) => {
|
||||
context.querySelectorAll(`.${tableRowClassName}[data-work-package-id="${workPackageId}"]`).forEach((el) => {
|
||||
el.classList.toggle(checkedClassName, selected);
|
||||
});
|
||||
|
||||
@@ -62,18 +62,18 @@ export class WorkPackageTable {
|
||||
) {
|
||||
}
|
||||
|
||||
public get renderedRows() {
|
||||
public get renderedRows():RenderedWorkPackage[] {
|
||||
return this.querySpace.tableRendered.getValueOr([]);
|
||||
}
|
||||
|
||||
public findRenderedRow(classIdentifier:string):[number, RenderedWorkPackage] {
|
||||
const index = _.findIndex(this.renderedRows, (row) => row.classIdentifier === classIdentifier);
|
||||
const index = this.renderedRows.findIndex((row) => row.classIdentifier === classIdentifier);
|
||||
|
||||
return [index, this.renderedRows[index]];
|
||||
}
|
||||
|
||||
public get rowBuilder():RowsBuilder {
|
||||
return _.find(this.builders, (builder:RowsBuilder) => builder.isApplicable(this))!;
|
||||
return this.builders.find((builder:RowsBuilder) => builder.isApplicable(this))!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +142,7 @@ export class WorkPackageTable {
|
||||
return;
|
||||
}
|
||||
|
||||
_.each(pass.renderedOrder, (row) => {
|
||||
pass.renderedOrder.forEach((row) => {
|
||||
if (row.workPackage?.id === workPackage.id!) {
|
||||
debugLog(`Refreshing rendered row ${row.classIdentifier}`);
|
||||
row.workPackage = workPackage;
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ export class WorkPackageTableEditingContext {
|
||||
public forms:Record<string, TableEditForm> = {};
|
||||
|
||||
public reset() {
|
||||
_.each(this.forms, (form) => form.destroy());
|
||||
Object.values(this.forms).forEach((form) => form.destroy());
|
||||
this.forms = {};
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -88,7 +88,7 @@ export class WorkPackageStatesInitializationService {
|
||||
public updateStatesFromForm(query:QueryResource, form:QueryFormResource) {
|
||||
const schema:QuerySchemaResource = form.schema as any;
|
||||
|
||||
_.each(schema.filtersSchemas.elements, (schema) => {
|
||||
schema.filtersSchemas.elements.forEach((schema) => {
|
||||
this.states.schemas.get(schema.href!).putValue(schema);
|
||||
});
|
||||
|
||||
@@ -107,7 +107,7 @@ export class WorkPackageStatesInitializationService {
|
||||
this.querySpace.tableRendered.clear('Clearing rendered data before upgrading query space');
|
||||
|
||||
if (results.schemas) {
|
||||
_.each(results.schemas.elements, (schema:SchemaResource) => {
|
||||
results.schemas.elements.forEach((schema:SchemaResource) => {
|
||||
this.states.schemas.get(schema.href!).putValue(schema);
|
||||
});
|
||||
}
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@ export class QueryFiltersService {
|
||||
* from the schema
|
||||
*/
|
||||
private getFilterSchema(filter:QueryFilterInstanceResource, form:QueryFormResource):QueryFilterInstanceSchemaResource|undefined {
|
||||
const available = form.$embedded.schema.filtersSchemas.elements;
|
||||
return _.find(available, (schema) => schema.allowedFilterValue.href === filter.filter.href);
|
||||
const available = form.$embedded.schema.filtersSchemas.elements as QueryFilterInstanceSchemaResource[];
|
||||
return available.find((schema) => schema.allowedFilterValue.href === filter.filter.href);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -112,20 +112,16 @@ export class UrlParamsHelperService {
|
||||
}
|
||||
|
||||
const parts:string[] = [];
|
||||
_.each(params, (value, key) => {
|
||||
Object.entries(params as Record<string, unknown>).forEach(([key, value]) => {
|
||||
if (value === undefined || value === null) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
const values:unknown[] = Array.isArray(value) ? value as unknown[] : [value];
|
||||
|
||||
_.each(value, (v) => {
|
||||
if (v !== null && typeof v === 'object') {
|
||||
v = JSON.stringify(v);
|
||||
}
|
||||
values.forEach((v) => {
|
||||
const encoded = (v !== null && typeof v === 'object') ? JSON.stringify(v) : v as string;
|
||||
parts.push(`${encodeURIComponent(key)}=${
|
||||
encodeURIComponent(v)}`);
|
||||
encodeURIComponent(encoded)}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -303,7 +299,7 @@ export class UrlParamsHelperService {
|
||||
// the array check is only there for backwards compatibility reasons.
|
||||
// Nowadays, it will always be an array;
|
||||
const vs = Array.isArray(urlFilter.v) ? urlFilter.v : [urlFilter.v];
|
||||
_.extend(attributes, { values: vs });
|
||||
Object.assign(attributes, { values: vs });
|
||||
}
|
||||
const filterData:any = {};
|
||||
filterData[urlFilter.n] = attributes;
|
||||
@@ -373,7 +369,7 @@ export class UrlParamsHelperService {
|
||||
queryData.sortBy = this.buildV3GetSortByFromQuery(query);
|
||||
queryData.timestamps = query.timestamps.join(',');
|
||||
|
||||
return _.extend(additionalParams, queryData);
|
||||
return Object.assign(additionalParams, queryData);
|
||||
}
|
||||
|
||||
public queryFilterValueToParam(value:HalResource|string|boolean):string {
|
||||
@@ -411,8 +407,8 @@ export class UrlParamsHelperService {
|
||||
const newFilters = filters.map((filter:QueryFilterInstanceResource) => {
|
||||
const id = this.buildV3GetFilterIdFromFilter(filter);
|
||||
const operator = this.buildV3GetOperatorIdFromFilter(filter);
|
||||
const values = this.buildV3GetValuesFromFilter(filter).map((value) => {
|
||||
_.each(replacements, (val:string, key:string) => {
|
||||
const values = this.buildV3GetValuesFromFilter(filter).map((value:string) => {
|
||||
Object.entries(replacements).forEach(([key, val]:[string, string]) => {
|
||||
value = value.replace(`{${key}}`, val);
|
||||
});
|
||||
|
||||
@@ -454,9 +450,9 @@ export class UrlParamsHelperService {
|
||||
|
||||
public buildV3GetValuesFromFilter(filter:QueryFilterInstanceResource|QueryFilterResource) {
|
||||
if (filter.values) {
|
||||
return _.map(filter.values, (v:any) => this.queryFilterValueToParam(v));
|
||||
return filter.values.map((v:any) => this.queryFilterValueToParam(v));
|
||||
}
|
||||
return _.map(filter._links.values, (v:any) => idFromLink(v.href as string));
|
||||
return filter._links.values.map((v:any) => idFromLink(v.href as string));
|
||||
}
|
||||
|
||||
private buildV3GetOperatorIdFromFilter(filter:QueryFilterInstanceResource) {
|
||||
|
||||
+2
-2
@@ -40,8 +40,8 @@ export class WorkPackageRelationsCountComponent extends UntilDestroyedMixin impl
|
||||
]).pipe(
|
||||
this.untilDestroyed(),
|
||||
).subscribe(([relations, workPackage]) => {
|
||||
const relationCount = _.size(relations);
|
||||
const childrenCount = _.size(workPackage.children);
|
||||
const relationCount = Object.keys(relations).length;
|
||||
const childrenCount = (workPackage.children ?? []).length;
|
||||
|
||||
this.count = relationCount + childrenCount;
|
||||
this.cdRef.markForCheck();
|
||||
|
||||
+1
-2
@@ -81,8 +81,7 @@ export class WorkPackageRelationRowComponent extends UntilDestroyedMixin impleme
|
||||
|
||||
this.userInputs.newRelationText = this.relation.description || '';
|
||||
this.availableRelationTypes = RelationResource.LOCALIZED_RELATION_TYPES(false);
|
||||
this.selectedRelationType = _.find(this.availableRelationTypes,
|
||||
{ name: this.relation.normalizedType(this.workPackage) })!;
|
||||
this.selectedRelationType = this.availableRelationTypes.find((relationType) => relationType.name === this.relation.normalizedType(this.workPackage))!;
|
||||
|
||||
this
|
||||
.apiV3Service
|
||||
|
||||
+3
-3
@@ -112,7 +112,7 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
|
||||
return;
|
||||
}
|
||||
|
||||
return _.find(relations, (relation:RelationResource) => {
|
||||
return Object.values(relations).find((relation:RelationResource) => {
|
||||
const denormalized = relation.denormalized(from);
|
||||
// Check that
|
||||
// 1. the denormalized relation points at "to"
|
||||
@@ -187,7 +187,7 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
|
||||
* @param relation
|
||||
*/
|
||||
private insertIntoStates(relation:RelationResource) {
|
||||
_.values(relation.ids).forEach((wpId) => {
|
||||
Object.values(relation.ids).forEach((wpId) => {
|
||||
this.multiState.get(wpId).doModify((value:RelationsStateValue) => {
|
||||
value[relation.id!] = relation;
|
||||
return value;
|
||||
@@ -204,7 +204,7 @@ export class WorkPackageRelationsService extends StateCacheService<RelationsStat
|
||||
* @param relation
|
||||
*/
|
||||
private removeFromStates(relation:RelationResource) {
|
||||
_.values(relation.ids).forEach((wpId) => {
|
||||
Object.values(relation.ids).forEach((wpId) => {
|
||||
this.multiState.get(wpId).doModify((value:RelationsStateValue) => {
|
||||
delete value[relation.id!];
|
||||
return value;
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ export class TabPortalOutlet {
|
||||
}
|
||||
|
||||
public get activeComponents():TabComponent[] {
|
||||
const tabs = _.values(this.activeTabs);
|
||||
const tabs = Object.values(this.activeTabs);
|
||||
return tabs.map((tab:ActiveTabInterface) => tab.componentRef.instance);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export class TabPortalOutlet {
|
||||
*/
|
||||
dispose():void {
|
||||
// Dispose all active tabs
|
||||
_.each(this.activeTabs, (active) => active.dispose());
|
||||
Object.values(this.activeTabs).forEach((active) => active.dispose());
|
||||
|
||||
// Remove outlet element
|
||||
if (this.outletElement.parentNode != null) {
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ export class WpTableConfigurationDisplaySettingsTabComponent implements TabCompo
|
||||
|
||||
public updateGroup(href:string) {
|
||||
this.displayMode = 'grouped';
|
||||
this.currentGroup = _.find(this.availableGroups, (group) => group.href === href) || null;
|
||||
this.currentGroup = this.availableGroups.find((group) => group.href === href) ?? null;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
+3
-3
@@ -94,7 +94,7 @@ export class WpTableConfigurationSortByTabComponent implements TabComponent, OnI
|
||||
|
||||
this.getManualSortingOption();
|
||||
|
||||
_.each(this.wpTableSortBy.current, (sort) => {
|
||||
this.wpTableSortBy.current.forEach((sort) => {
|
||||
if (!sort.column.href!.endsWith('/parent')) {
|
||||
this.sortationObjects.push(
|
||||
new SortModalObject({ name: sort.column.name, href: sort.column.href },
|
||||
@@ -113,7 +113,7 @@ export class WpTableConfigurationSortByTabComponent implements TabComponent, OnI
|
||||
}
|
||||
|
||||
public updateSelection(sort:SortModalObject, selected:string | null) {
|
||||
sort.column = _.find(this.allColumns, (column) => column.href === selected) || this.emptyColumn;
|
||||
sort.column = this.allColumns.find((column) => column.href === selected) ?? this.emptyColumn;
|
||||
this.updateUsedColumns();
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export class WpTableConfigurationSortByTabComponent implements TabComponent, OnI
|
||||
}
|
||||
|
||||
private getMatchingSort(column:string, direction:string) {
|
||||
return _.find(this.wpTableSortBy.available, (sort) => sort.column.href === column && sort.direction.href === direction);
|
||||
return this.wpTableSortBy.available.find((sort) => sort.column.href === column && sort.direction.href === direction);
|
||||
}
|
||||
|
||||
private fillUpSortElements() {
|
||||
|
||||
+3
-3
@@ -77,7 +77,7 @@ export class WpTableConfigurationRelationSelectorComponent implements OnInit {
|
||||
private setSelectedRelationFilter():void {
|
||||
const currentRelationFilters:QueryFilterInstanceResource[] = this.relationFiltersOf(this.wpTableFilters.current) as QueryFilterInstanceResource[];
|
||||
if (currentRelationFilters.length > 0) {
|
||||
this.selectedRelationFilter = _.find(this.availableRelationFilters, { id: currentRelationFilters[0].id })!;
|
||||
this.selectedRelationFilter = this.availableRelationFilters.find((relationFilter) => relationFilter.id === currentRelationFilters[0].id)!;
|
||||
} else {
|
||||
this.selectedRelationFilter = this.availableRelationFilters[0];
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export class WpTableConfigurationRelationSelectorComponent implements OnInit {
|
||||
}
|
||||
|
||||
private relationFiltersOf(filters:QueryFilterResource[]|QueryFilterInstanceResource[]):QueryFilterResource[]|QueryFilterInstanceResource[] {
|
||||
return _.filter(filters, (filter:QueryFilterResource|QueryFilterInstanceResource) => _.includes(this.relationFilterIds, filter.id));
|
||||
return filters.filter((filter:QueryFilterResource|QueryFilterInstanceResource) => this.relationFilterIds.includes(filter.id));
|
||||
}
|
||||
|
||||
private addFilterToCurrentState(filter:QueryFilterResource):void {
|
||||
@@ -110,7 +110,7 @@ export class WpTableConfigurationRelationSelectorComponent implements OnInit {
|
||||
}
|
||||
|
||||
private getOperatorForId(filter:QueryFilterResource, id:string):QueryOperatorResource {
|
||||
return _.find(this.schemaCache.of(filter).availableOperators, { id }) as QueryOperatorResource;
|
||||
return (this.schemaCache.of(filter).availableOperators as QueryOperatorResource[]).find((operator:QueryOperatorResource) => operator.id === id)!;
|
||||
}
|
||||
|
||||
public compareRelationFilters(f1:undefined|QueryFilterResource, f2:undefined|QueryFilterResource):boolean {
|
||||
|
||||
+9
-9
@@ -98,7 +98,7 @@ export class WorkPackageContextMenuHelperService {
|
||||
|
||||
allowedActions = allowedActions.concat(this.getAllowedRelationActions(workPackage, allowSplitScreenActions));
|
||||
|
||||
_.each(allowedActions, (allowedAction) => {
|
||||
allowedActions.forEach((allowedAction) => {
|
||||
singularPermittedActions.push({
|
||||
key: allowedAction.key,
|
||||
text: allowedAction.text,
|
||||
@@ -123,12 +123,12 @@ export class WorkPackageContextMenuHelperService {
|
||||
return link;
|
||||
}
|
||||
|
||||
public getIntersectOfPermittedActions(workPackages:any) {
|
||||
const bulkPermittedActions:any = [];
|
||||
const possibleActions = this.BULK_ACTIONS.concat(this.HookService.call('workPackageBulkContextMenu'));
|
||||
const permittedActions = _.filter(possibleActions, (action:any) => _.every(workPackages, (workPackage:WorkPackageResource) => this.isActionAllowed(workPackage, action)));
|
||||
public getIntersectOfPermittedActions(workPackages:WorkPackageResource[]) {
|
||||
const bulkPermittedActions:WorkPackageAction[] = [];
|
||||
const possibleActions:WorkPackageAction[] = this.BULK_ACTIONS.concat(this.HookService.call('workPackageBulkContextMenu'));
|
||||
const permittedActions = possibleActions.filter((action) => workPackages.every((workPackage:WorkPackageResource) => this.isActionAllowed(workPackage, action)));
|
||||
|
||||
_.each(permittedActions, (permittedAction:any) => {
|
||||
permittedActions.forEach((permittedAction) => {
|
||||
bulkPermittedActions.push({
|
||||
key: permittedAction.key,
|
||||
text: permittedAction.text,
|
||||
@@ -154,20 +154,20 @@ export class WorkPackageContextMenuHelperService {
|
||||
}
|
||||
|
||||
private isActionAllowed(workPackage:WorkPackageResource, action:WorkPackageAction):boolean {
|
||||
return _.filter(this.getAllowedActions(workPackage, [action]), (a) => a === action).length >= 1;
|
||||
return this.getAllowedActions(workPackage, [action]).filter((a) => a === action).length >= 1;
|
||||
}
|
||||
|
||||
private getAllowedActions(workPackage:WorkPackageResource, actions:WorkPackageAction[]):WorkPackageAction[] {
|
||||
const allowedActions:WorkPackageAction[] = [];
|
||||
|
||||
_.each(actions, (action) => {
|
||||
actions.forEach((action) => {
|
||||
if (action.link && workPackage[action.link] !== undefined) {
|
||||
action.text = action.text || I18n.t(`js.button_${action.key}`);
|
||||
allowedActions.push(action);
|
||||
}
|
||||
});
|
||||
|
||||
_.each(this.HookService.call('workPackageTableContextMenu'), (action:WorkPackageAction) => {
|
||||
this.HookService.call('workPackageTableContextMenu').forEach((action:WorkPackageAction) => {
|
||||
if (workPackage[action.link!] !== undefined) {
|
||||
const index = action.indexBy ? action.indexBy(allowedActions) : allowedActions.length;
|
||||
allowedActions.splice(index, 0, action);
|
||||
|
||||
+1
-1
@@ -462,7 +462,7 @@ export class TimelineCellRenderer {
|
||||
|
||||
// ensure minimum width
|
||||
if (!_.isNaN(start.valueOf()) || !_.isNaN(due.valueOf())) {
|
||||
const minWidth = _.max([renderInfo.viewParams.pixelPerDay, 2]);
|
||||
const minWidth = Math.max(renderInfo.viewParams.pixelPerDay, 2);
|
||||
element.style.minWidth = `${minWidth}px`;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -268,7 +268,7 @@ export function registerWorkPackageMouseHandler(this:void,
|
||||
.save<WorkPackageResource, WorkPackageChangeset>(change)
|
||||
.then((result) => {
|
||||
notificationService.showSave(result.resource);
|
||||
const ids = _.map(querySpace.tableRendered.value, (row) => row.workPackageId);
|
||||
const ids = (querySpace.tableRendered.value ?? []).map((row) => row.workPackageId);
|
||||
return apiv3Service
|
||||
.work_packages
|
||||
.filterUpdatedSince(ids, updatedAt)
|
||||
|
||||
+4
-4
@@ -59,7 +59,7 @@ export class WorkPackageTimelineCellsRenderer {
|
||||
}
|
||||
|
||||
public getCellsFor(wpId:string):WorkPackageTimelineCell[] {
|
||||
return _.filter(this.cells, (cell) => cell.workPackageId === wpId) || [];
|
||||
return Object.values(this.cells).filter((cell) => cell.workPackageId === wpId) || [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,11 +70,11 @@ export class WorkPackageTimelineCellsRenderer {
|
||||
this.synchronizeCells();
|
||||
|
||||
// Update all cells
|
||||
_.each(this.cells, (cell) => this.refreshSingleCell(cell));
|
||||
Object.values(this.cells).forEach((cell) => this.refreshSingleCell(cell));
|
||||
}
|
||||
|
||||
public refreshCellsFor(wpId:string) {
|
||||
_.each(this.getCellsFor(wpId), (cell) => this.refreshSingleCell(cell));
|
||||
this.getCellsFor(wpId).forEach((cell) => this.refreshSingleCell(cell));
|
||||
}
|
||||
|
||||
public refreshSingleCell(cell:WorkPackageTimelineCell, isDuplicatedCell?:boolean, withAlternativeLabels?:boolean) {
|
||||
@@ -95,7 +95,7 @@ export class WorkPackageTimelineCellsRenderer {
|
||||
const currentlyActive:string[] = Object.keys(this.cells);
|
||||
const newCells:string[] = [];
|
||||
|
||||
_.each(this.wpTimeline.workPackageIdOrder, (renderedRow:RenderedWorkPackage) => {
|
||||
this.wpTimeline.workPackageIdOrder.forEach((renderedRow:RenderedWorkPackage) => {
|
||||
const wpId = renderedRow.workPackageId;
|
||||
|
||||
// Ignore extra rows not tied to a work package
|
||||
|
||||
+2
-2
@@ -264,7 +264,7 @@ export class WorkPackageTimelineTableController extends UntilDestroyedMixin impl
|
||||
// Update all cells
|
||||
this.cellsRenderer.refreshAllCells();
|
||||
|
||||
_.each(this.renderers, (cb, key) => {
|
||||
Object.entries(this.renderers).forEach(([key, cb]) => {
|
||||
debugLog(`Refreshing timeline member ${key}`);
|
||||
cb(this._viewParameters);
|
||||
});
|
||||
@@ -485,7 +485,7 @@ export class WorkPackageTimelineTableController extends UntilDestroyedMixin impl
|
||||
const pixelPerDay = getPixelPerDayForZoomLevel(zoomLevel);
|
||||
const visibleDays = timelineWidthInPx / pixelPerDay;
|
||||
|
||||
if (visibleDays >= daysSpan || zoomLevel === _.last(zoomLevelOrder)) {
|
||||
if (visibleDays >= daysSpan || zoomLevel === zoomLevelOrder.at(-1)) {
|
||||
// Zoom level is enough
|
||||
const previousZoomLevel = this._viewParameters.settings.zoomLevel;
|
||||
|
||||
|
||||
+3
-3
@@ -166,8 +166,8 @@ export class WorkPackageTableTimelineRelations extends UntilDestroyedMixin imple
|
||||
}
|
||||
|
||||
this.removeRelationElementsForWorkPackage(workPackageId);
|
||||
const relations = _.values(workPackageWithRelation);
|
||||
const relationsList = _.values(relations);
|
||||
const relations = Object.values(workPackageWithRelation);
|
||||
const relationsList = Object.values(relations);
|
||||
relationsList.forEach((relation) => {
|
||||
if (!(relation.type === 'precedes'
|
||||
|| relation.type === 'follows')) {
|
||||
@@ -196,7 +196,7 @@ export class WorkPackageTableTimelineRelations extends UntilDestroyedMixin imple
|
||||
}
|
||||
|
||||
private renderElements() {
|
||||
const wpIdsWithRelations:string[] = _.keys(this.workPackagesWithRelations);
|
||||
const wpIdsWithRelations:string[] = Object.keys(this.workPackagesWithRelations);
|
||||
this.renderWorkPackagesRelations(wpIdsWithRelations);
|
||||
}
|
||||
|
||||
|
||||
@@ -163,11 +163,8 @@ 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)),
|
||||
inViewport,
|
||||
[lastRest].filter((e) => !_.isNil(e)),
|
||||
);
|
||||
const inViewportAndBoundaries = [firstRest].filter((e) => e != null)
|
||||
.concat(inViewport, [lastRest].filter((e) => e != null));
|
||||
|
||||
return {
|
||||
inViewportAndBoundaries,
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ export class WorkPackageTableConfiguration {
|
||||
public filterButtonText:string = I18n.t('js.button_filter');
|
||||
|
||||
constructor(providedConfig:WorkPackageTableConfigurationObject) {
|
||||
_.each(providedConfig, (value, k) => {
|
||||
Object.entries(providedConfig).forEach(([k, value]) => {
|
||||
const key = (k as keyof WorkPackageTableConfiguration);
|
||||
(this as any)[key] = value;
|
||||
});
|
||||
|
||||
+2
-2
@@ -147,8 +147,8 @@ export class WorkPackageViewAdditionalElementsService {
|
||||
*/
|
||||
private getInvolvedWorkPackages(states:RelationsStateValue[]) {
|
||||
const ids:string[] = [];
|
||||
_.each(states, (relations:RelationsStateValue) => {
|
||||
_.each(relations, (resource:RelationResource) => {
|
||||
states.forEach((relations:RelationsStateValue) => {
|
||||
Object.values(relations).forEach((resource:RelationResource) => {
|
||||
ids.push(resource.ids.from, resource.ids.to);
|
||||
});
|
||||
});
|
||||
|
||||
+7
-7
@@ -79,21 +79,21 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService<
|
||||
queryColumnTypes.RELATION_OF_TYPE,
|
||||
queryColumnTypes.RELATION_TO_TYPE,
|
||||
];
|
||||
return !!_.find(this.getColumns(), (c) => relationColumns.includes(c._type));
|
||||
return !!this.getColumns().find((c) => relationColumns.includes(c._type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current set of columns include child relations
|
||||
*/
|
||||
public hasChildRelationsColumn() {
|
||||
return !!_.find(this.getColumns(), (c) => c._type === queryColumnTypes.RELATION_CHILD);
|
||||
return !!this.getColumns().find((c) => c._type === queryColumnTypes.RELATION_CHILD);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current set of columns include shares
|
||||
*/
|
||||
public hasShareColumn() {
|
||||
return !!_.find(this.getColumns(), (c) => c.id === sharedUserColumn.id);
|
||||
return !!this.getColumns().find((c) => c.id === sharedUserColumn.id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,7 +108,7 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService<
|
||||
* Return the index of the given column or -1 if it is not contained.
|
||||
*/
|
||||
public index(id:string):number {
|
||||
return _.findIndex(this.getColumns(), (column) => column.id === id);
|
||||
return this.getColumns().findIndex((column) => column.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,7 +116,7 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService<
|
||||
* @param id
|
||||
*/
|
||||
public findById(id:string):QueryColumn|undefined {
|
||||
return _.find(this.getColumns(), (column) => column.id === id);
|
||||
return this.getColumns().find((column) => column.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -174,7 +174,7 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService<
|
||||
}
|
||||
|
||||
public setColumnsById(columnIds:string[]) {
|
||||
const mapped = columnIds.map((id) => _.find(this.all, (c) => c.id === id));
|
||||
const mapped = columnIds.map((id) => this.all.find((c) => c.id === id));
|
||||
this.setColumns(_.compact(mapped));
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ export class WorkPackageViewColumnsService extends WorkPackageQueryStateService<
|
||||
}
|
||||
|
||||
if (this.index(id) === -1) {
|
||||
const newColumn = _.find(this.all, (column) => column.id === id);
|
||||
const newColumn = this.all.find((column) => column.id === id);
|
||||
|
||||
if (!newColumn) {
|
||||
throw new Error('Column with provided name is not found');
|
||||
|
||||
+3
-5
@@ -153,9 +153,7 @@ export class WorkPackageViewFiltersService extends WorkPackageQueryStateService<
|
||||
public instantiate(filterOrId:QueryFilterResource|string):QueryFilterInstanceResource {
|
||||
const id = (filterOrId instanceof QueryFilterResource) ? filterOrId.id : filterOrId;
|
||||
|
||||
const schema = _.find(
|
||||
this.availableSchemas,
|
||||
(schema) => (schema.filter.allowedValues as HalResource)[0].id === id,
|
||||
const schema = this.availableSchemas.find((schema) => (schema.filter.allowedValues as HalResource[])[0].id === id,
|
||||
)!;
|
||||
|
||||
return schema.getFilter();
|
||||
@@ -201,7 +199,7 @@ export class WorkPackageViewFiltersService extends WorkPackageQueryStateService<
|
||||
* @param filters
|
||||
*/
|
||||
public isComplete(filters:QueryFilterInstanceResource[]):boolean {
|
||||
return _.every(filters, (filter) => filter.isCompletelyDefined());
|
||||
return filters.every((filter) => filter.isCompletelyDefined());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -247,7 +245,7 @@ export class WorkPackageViewFiltersService extends WorkPackageQueryStateService<
|
||||
* @param id Identifier of the filter
|
||||
*/
|
||||
public findIndex(id:string):number {
|
||||
return _.findIndex(this.current, (f) => f.id === id);
|
||||
return this.current.findIndex((f) => f.id === id);
|
||||
}
|
||||
|
||||
public applyToQuery(query:QueryResource):boolean {
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ export class WorkPackageViewGroupByService extends WorkPackageQueryStateService<
|
||||
}
|
||||
|
||||
public isGroupable(column:QueryColumn):boolean {
|
||||
return !!_.find(this.available, (candidate) => candidate.id === column.id);
|
||||
return !!this.available.find((candidate) => candidate.id === column.id);
|
||||
}
|
||||
|
||||
public disable() {
|
||||
@@ -66,7 +66,7 @@ export class WorkPackageViewGroupByService extends WorkPackageQueryStateService<
|
||||
}
|
||||
|
||||
public setBy(column:QueryColumn) {
|
||||
const groupBy = _.find(this.available, (candidate) => candidate.id === column.id);
|
||||
const groupBy = this.available.find((candidate) => candidate.id === column.id);
|
||||
|
||||
if (groupBy) {
|
||||
this.update(groupBy);
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ export class WorkPackageViewHighlightingService extends WorkPackageQueryStateSer
|
||||
}
|
||||
|
||||
// 3. Is name in selected attributes ?
|
||||
return !!_.find(this.current.selectedAttributes, (attr:HalResource) => attr.id === name);
|
||||
return !!this.current.selectedAttributes?.find((attr:HalResource) => attr.id === name);
|
||||
}
|
||||
|
||||
public get current():WorkPackageViewHighlight {
|
||||
|
||||
+4
-6
@@ -89,10 +89,8 @@ workPackage:WorkPackageResource,
|
||||
const type = this.relationColumnType(column);
|
||||
|
||||
if (type !== null) {
|
||||
_.each(
|
||||
this.relationsForColumn(workPackage, relations, column),
|
||||
(relation) => eachCallback(relation, column, type),
|
||||
);
|
||||
this.relationsForColumn(workPackage, relations, column)
|
||||
.forEach((relation:RelationResource) => eachCallback(relation, column, type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +112,7 @@ this.relationsForColumn(workPackage, relations, column),
|
||||
if (type === 'toType') {
|
||||
const typeHref = (column as TypeRelationQueryColumn).type.href;
|
||||
|
||||
return _.filter(relations, (relation:RelationResource) => {
|
||||
return Object.values(relations).filter((relation:RelationResource) => {
|
||||
const denormalized = relation.denormalized(workPackage);
|
||||
const target = this.apiV3Service.work_packages.cache.state(denormalized.targetId).value;
|
||||
|
||||
@@ -126,7 +124,7 @@ this.relationsForColumn(workPackage, relations, column),
|
||||
if (type === 'ofType') {
|
||||
const { relationType } = column as RelationQueryColumn;
|
||||
|
||||
return _.filter(relations, (relation:RelationResource) => relation.denormalized(workPackage).relationType === relationType);
|
||||
return Object.values(relations).filter((relation:RelationResource) => relation.denormalized(workPackage).relationType === relationType);
|
||||
}
|
||||
|
||||
return [];
|
||||
|
||||
+2
-2
@@ -73,7 +73,7 @@ export class WorkPackageViewSelectionService extends WorkPackageViewBaseService<
|
||||
public getSelectedWorkPackageIds():string[] {
|
||||
const selected:string[] = [];
|
||||
|
||||
_.each(this.current?.selected, (isSelected:boolean, wpId:string) => {
|
||||
Object.entries(this.current?.selected ?? {}).forEach(([wpId, isSelected]) => {
|
||||
if (isSelected) {
|
||||
selected.push(wpId);
|
||||
}
|
||||
@@ -97,7 +97,7 @@ export class WorkPackageViewSelectionService extends WorkPackageViewBaseService<
|
||||
* Return the number of selected rows.
|
||||
*/
|
||||
public get selectionCount():number {
|
||||
return _.size(this.current?.selected);
|
||||
return Object.keys(this.current?.selected ?? {}).length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-7
@@ -73,9 +73,7 @@ export class WorkPackageViewSortByService extends WorkPackageQueryStateService<Q
|
||||
}
|
||||
|
||||
public isSortable(column:QueryColumn):boolean {
|
||||
return !!_.find(
|
||||
this.available,
|
||||
(candidate) => candidate.column.href === column.href,
|
||||
return !!this.available.find((candidate) => candidate.column.href === column.href,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -96,9 +94,7 @@ export class WorkPackageViewSortByService extends WorkPackageQueryStateService<Q
|
||||
}
|
||||
|
||||
public findAvailableDirection(column:QueryColumn, direction:string):QuerySortByResource | undefined {
|
||||
return _.find(
|
||||
this.available,
|
||||
(candidate) => (candidate.column.href === column.href
|
||||
return this.available.find((candidate) => (candidate.column.href === column.href
|
||||
&& candidate.direction.href === direction),
|
||||
);
|
||||
}
|
||||
@@ -146,6 +142,6 @@ export class WorkPackageViewSortByService extends WorkPackageQueryStateService<Q
|
||||
}
|
||||
|
||||
private get manualSortObject() {
|
||||
return _.find(this.available, (sort) => sort.column.href!.endsWith('/manualSorting'));
|
||||
return this.available.find((sort) => sort.column.href!.endsWith('/manualSorting'));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ export class WorkPackageViewTimelineService extends WorkPackageQueryStateService
|
||||
public getNormalizedLabels(workPackage:WorkPackageResource) {
|
||||
const labels:TimelineLabels = this.defaultLabels;
|
||||
|
||||
_.each(this.current.labels, (attribute:string | null, positionAsString:string) => {
|
||||
Object.entries(this.current.labels).forEach(([positionAsString, attribute]) => {
|
||||
// RR: Lodash typings declare the position as string. However, it is save to cast
|
||||
// to `keyof TimelineLabels` because `this.current.labels` is of type TimelineLabels.
|
||||
const position:keyof TimelineLabels = positionAsString as keyof TimelineLabels;
|
||||
|
||||
@@ -76,7 +76,7 @@ export class DatePicker {
|
||||
}
|
||||
});
|
||||
|
||||
const mergedOptions = _.extend({}, this.defaultOptions, options);
|
||||
const mergedOptions = Object.assign({}, this.defaultOptions, options);
|
||||
|
||||
let datePickerInstances:Instance|Instance[];
|
||||
if (this.datepickerTarget) {
|
||||
|
||||
@@ -21,7 +21,7 @@ export class Changeset {
|
||||
* @returns {string[]}
|
||||
*/
|
||||
public get changed():string[] {
|
||||
return _.keys(this.changes);
|
||||
return Object.keys(this.changes);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -219,7 +219,7 @@ export class ResourceChangeset<T extends HalResource = HalResource> {
|
||||
public get changes():Record<string, unknown> {
|
||||
const changes:Record<string, unknown> = {};
|
||||
|
||||
_.each(this.changeset.all, (item, key) => {
|
||||
Object.entries(this.changeset.all).forEach(([key, item]) => {
|
||||
changes[key] = item.to;
|
||||
});
|
||||
|
||||
@@ -389,7 +389,7 @@ export class ResourceChangeset<T extends HalResource = HalResource> {
|
||||
reference = this.form$.value.payload.$source;
|
||||
}
|
||||
|
||||
_.each(this.changeset.all, (val:ChangeItem, key:string) => {
|
||||
Object.entries(this.changeset.all).forEach(([key, val]) => {
|
||||
if (!this.schema.isAttributeEditable(key)) {
|
||||
debugLog(`Trying to write ${key} but is not writable in schema`);
|
||||
return;
|
||||
@@ -495,7 +495,7 @@ export class ResourceChangeset<T extends HalResource = HalResource> {
|
||||
* that we need to set.
|
||||
*/
|
||||
protected setNewDefaults(form:FormResource) {
|
||||
_.each(form.payload, (val:unknown, key:string) => {
|
||||
Object.entries(form.payload as Record<string, unknown>).forEach(([key, val]) => {
|
||||
const fieldSchema:IFieldSchema|null = this.schema.ofProperty(key);
|
||||
if (!fieldSchema?.writable && !fieldSchema?.required) {
|
||||
return;
|
||||
|
||||
@@ -208,7 +208,7 @@ export class EditFormComponent extends EditForm<HalResource> implements OnInit,
|
||||
|
||||
public register(field:EditableAttributeFieldComponent) {
|
||||
this.fields[field.fieldName] = field;
|
||||
this.registeredFields.putValue(_.keys(this.fields));
|
||||
this.registeredFields.putValue(Object.keys(this.fields));
|
||||
|
||||
const shouldActivate = (this.editMode && !this.skipField(field) || this.activeFields[field.fieldName]);
|
||||
|
||||
@@ -228,7 +228,7 @@ export class EditFormComponent extends EditForm<HalResource> implements OnInit,
|
||||
}
|
||||
|
||||
public start() {
|
||||
_.each(this.fields, (ctrl) => this.activate(ctrl.fieldName));
|
||||
Object.values(this.fields).forEach((ctrl) => { void this.activate(ctrl.fieldName); });
|
||||
}
|
||||
|
||||
protected focusOnFirstError():void {
|
||||
|
||||
@@ -151,7 +151,7 @@ export abstract class EditForm<T extends HalResource = HalResource> {
|
||||
return this.change.getForm().then((form:FormResource) => {
|
||||
const activateFields:Promise<unknown>[] = [];
|
||||
|
||||
_.each(form.validationErrors, (_:ErrorResource, key:string) => {
|
||||
Object.entries(form.validationErrors).forEach(([key]) => {
|
||||
if (key === 'id') {
|
||||
return;
|
||||
}
|
||||
@@ -183,10 +183,10 @@ export abstract class EditForm<T extends HalResource = HalResource> {
|
||||
this.errorsPerAttribute = {};
|
||||
|
||||
// Notify all fields of upcoming save
|
||||
const openFields = _.keys(this.activeFields);
|
||||
const openFields = Object.keys(this.activeFields);
|
||||
|
||||
// Call onSubmit handlers
|
||||
await Promise.all(_.map(this.activeFields, (handler:EditFieldHandler) => handler.onSubmit()));
|
||||
await Promise.all(Object.values(this.activeFields).map((handler:EditFieldHandler) => handler.onSubmit()));
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.halEditing.save<T, ResourceChangeset<T>>(this.change)
|
||||
@@ -231,7 +231,7 @@ export abstract class EditForm<T extends HalResource = HalResource> {
|
||||
*/
|
||||
public closeEditFields(fields:string[]|'all' = 'all', resetChange = true) {
|
||||
if (fields === 'all') {
|
||||
fields = _.keys(this.activeFields);
|
||||
fields = Object.keys(this.activeFields);
|
||||
}
|
||||
|
||||
fields.forEach((name:string) => {
|
||||
|
||||
+6
-4
@@ -124,7 +124,7 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
|
||||
public set selectedOption(val:ValueOption[]) {
|
||||
this._selectedOption = val;
|
||||
const mapper = (val:ValueOption) => {
|
||||
const option = _.find(this.availableOptions, (o) => o.href === val.href) || this.nullOption;
|
||||
const option = (this.availableOptions as ValueOption[]).find((o) => o.href === val.href) ?? this.nullOption;
|
||||
|
||||
// Special case 'null' value, which angular
|
||||
// only understands in ng-options as an empty string.
|
||||
@@ -161,10 +161,10 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
|
||||
}
|
||||
|
||||
private findValueOption(option?:HalResource):ValueOption {
|
||||
let result;
|
||||
let result:ValueOption|undefined;
|
||||
|
||||
if (option) {
|
||||
result = _.find(this.availableOptions, (valueOption) => valueOption.href === option.href)!;
|
||||
result = (this.availableOptions as ValueOption[]).find((valueOption) => valueOption.href === option.href)!;
|
||||
}
|
||||
|
||||
return result || this.nullOption;
|
||||
@@ -225,10 +225,12 @@ export class MultiSelectEditFieldComponent extends EditFieldComponent implements
|
||||
|
||||
private checkCurrentValueValidity() {
|
||||
if (this.value) {
|
||||
const resourceValue = (this.resource as Record<string, HalResource[]|HalResource>)[this.name];
|
||||
const values = Array.isArray(resourceValue) ? resourceValue : [resourceValue];
|
||||
this.currentValueInvalid = !!(
|
||||
// (If value AND)
|
||||
// MultiSelect AND there is no value which href is not in the options hrefs
|
||||
(!_.some(this.value, (value:HalResource) => _.some(this.availableOptions, (option) => (option.href === value.href))))
|
||||
(!values.some((value:HalResource) => (this.availableOptions as ValueOption[]).some((option) => (option.href === value.href))))
|
||||
);
|
||||
} else {
|
||||
// If no value but required
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ export class SelectAutocompleterRegisterService {
|
||||
}
|
||||
|
||||
public getAutocompleterOfAttribute(attribute:string) {
|
||||
const assignment = _.find(this._fields, (field) => field.attribute === attribute);
|
||||
const assignment = this._fields.find((field) => field.attribute === attribute);
|
||||
return assignment ? assignment.component : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -86,11 +86,11 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
|
||||
|
||||
public get selectedOption() {
|
||||
const href = this.value ? this.value.href : null;
|
||||
return _.find(this.availableOptions, (o) => o.href === href)!;
|
||||
return (this.availableOptions as ValueOption[]).find((o) => o.href === href)!;
|
||||
}
|
||||
|
||||
public set selectedOption(val:ValueOption|HalResource) {
|
||||
const option = _.find(this.availableOptions, (o) => o.href === val.href);
|
||||
const option = (this.availableOptions as ValueOption[]).find((o) => o.href === val.href);
|
||||
|
||||
// Special case 'null' value, which angular
|
||||
// only understands in ng-options as an empty string.
|
||||
@@ -217,7 +217,7 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
|
||||
|
||||
public get currentValueInvalid():boolean {
|
||||
return !!(
|
||||
(this.value && !_.some(this.availableOptions, (option:HalResource) => (option.href === this.value.href)))
|
||||
(this.value && !this.availableOptions.some((option:HalResource) => (option.href === (this.value as HalResource).href)))
|
||||
|| (!this.value && this.schema.required)
|
||||
);
|
||||
}
|
||||
@@ -298,8 +298,8 @@ export class SelectEditFieldComponent extends EditFieldComponent implements OnIn
|
||||
return {};
|
||||
}
|
||||
|
||||
private getEmptyOption():undefined {
|
||||
return _.find(this.availableOptions, (el) => el.name === this.text.placeholder);
|
||||
private getEmptyOption():ValueOption|undefined {
|
||||
return (this.availableOptions as ValueOption[]).find((el) => el.name === this.text.placeholder);
|
||||
}
|
||||
|
||||
private syncUrlParamsOnChangeIfNeeded(fieldName:string, editMode?:boolean) {
|
||||
|
||||
@@ -44,7 +44,7 @@ export class GridWidgetsService {
|
||||
private buildWidgets() {
|
||||
let registeredWidgets:WidgetRegistration[] = this.buildDefaultWidgets();
|
||||
|
||||
_.each(this.Hook.call('gridWidgets'), (registration:WidgetRegistration[]) => {
|
||||
this.Hook.call('gridWidgets').forEach((registration:WidgetRegistration[]) => {
|
||||
registeredWidgets = registeredWidgets.concat(registration);
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -153,7 +153,7 @@ export class WorkPackageSingleContextMenuDirective extends OpContextMenuTrigger
|
||||
actions = this.addTimerAction(actions);
|
||||
|
||||
// Splice plugin actions onto the core actions
|
||||
_.each(this.getPermittedPluginActions(authorization), (action:WorkPackageAction) => {
|
||||
this.getPermittedPluginActions(authorization).forEach((action:WorkPackageAction) => {
|
||||
const index = action.indexBy ? action.indexBy(actions) : actions.length;
|
||||
actions.splice(index, 0, action);
|
||||
});
|
||||
|
||||
+1
-1
@@ -132,7 +132,7 @@ export class RemoteFieldUpdaterComponent implements OnInit, OnDestroy {
|
||||
this
|
||||
.request(params)
|
||||
.subscribe((response:object) => {
|
||||
_.each(response, (val:string, selector:string) => {
|
||||
Object.entries(response).forEach(([selector, val]) => {
|
||||
const element = document.getElementById(selector) as HTMLElement|HTMLInputElement;
|
||||
|
||||
if (element instanceof HTMLInputElement) {
|
||||
|
||||
+2
-2
@@ -193,12 +193,12 @@ export class WorkPackageEmbeddedGraphComponent implements OnChanges {
|
||||
}
|
||||
|
||||
public get chartDescription():string {
|
||||
const chartDataDescriptions = _.map(this.chartLabels, (label, index) => {
|
||||
const chartDataDescriptions = this.chartLabels.map((label, index) => {
|
||||
if (this.chartData.length === 1) {
|
||||
const allCount = this.chartData[0].data[index];
|
||||
return `${allCount} ${label}`;
|
||||
}
|
||||
const labelCounts = _.map(this.chartData, (dataset) => `${dataset.data[index]} ${dataset.label}`);
|
||||
const labelCounts = this.chartData.map((dataset) => `${dataset.data[index]} ${dataset.label}`);
|
||||
return `${label}: ${labelCounts.join(', ')}`;
|
||||
});
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export class KeyboardShortcutService {
|
||||
public register():void {
|
||||
void this.configurationService.initialize().then(() => {
|
||||
if (!this.configurationService.disableKeyboardShortcuts()) {
|
||||
_.each(this.shortcuts, (action:() => void, key:string) => Mousetrap.bind(key, action));
|
||||
Object.entries(this.shortcuts).forEach(([key, action]) => Mousetrap.bind(key, action));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
//-- copyright
|
||||
// OpenProject is an open source project management software.
|
||||
// Copyright (C) the OpenProject GmbH
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See COPYRIGHT and LICENSE files for more details.
|
||||
//++
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
ApiV3FilterBuilder,
|
||||
FalseValue,
|
||||
TrueValue,
|
||||
} from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
|
||||
|
||||
// Exercises the object-iteration helpers migrated off lodash (Object.entries
|
||||
// over the filter map): the `filters` getter and `toFilterObject`.
|
||||
describe('ApiV3FilterBuilder', () => {
|
||||
describe('add() and the filters getter', () => {
|
||||
it('serialises each entry of the filter map to a single-key object', () => {
|
||||
const builder = new ApiV3FilterBuilder()
|
||||
.add('status', '=', ['1'])
|
||||
.add('subject', '~', ['foo']);
|
||||
|
||||
expect(builder.filters).toEqual([
|
||||
{ status: { operator: '=', values: ['1'] } },
|
||||
{ subject: { operator: '~', values: ['foo'] } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('preserves insertion order of the filter map', () => {
|
||||
const builder = new ApiV3FilterBuilder()
|
||||
.add('c', '=', ['3'])
|
||||
.add('a', '=', ['1'])
|
||||
.add('b', '=', ['2']);
|
||||
|
||||
expect(builder.filters.map((f) => Object.keys(f)[0])).toEqual(['c', 'a', 'b']);
|
||||
});
|
||||
|
||||
it('maps boolean filter values to the lodash truthy/falsy sentinels', () => {
|
||||
const builder = new ApiV3FilterBuilder()
|
||||
.add('open', '=', true)
|
||||
.add('closed', '=', false);
|
||||
|
||||
expect(builder.filters).toEqual([
|
||||
{ open: { operator: '=', values: TrueValue } },
|
||||
{ closed: { operator: '=', values: FalseValue } },
|
||||
]);
|
||||
});
|
||||
|
||||
it('returns an empty array when no filters were added', () => {
|
||||
expect(new ApiV3FilterBuilder().filters).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toFilterObject', () => {
|
||||
it('flattens an array of single-key filters into one map', () => {
|
||||
const map = ApiV3FilterBuilder.toFilterObject([
|
||||
{ status: { operator: '=', values: ['1'] } },
|
||||
{ subject: { operator: '~', values: ['foo'] } },
|
||||
]);
|
||||
|
||||
expect(map).toEqual({
|
||||
status: { operator: '=', values: ['1'] },
|
||||
subject: { operator: '~', values: ['foo'] },
|
||||
});
|
||||
});
|
||||
|
||||
it('iterates every key of a multi-key filter item', () => {
|
||||
const map = ApiV3FilterBuilder.toFilterObject([
|
||||
{
|
||||
status: { operator: '=', values: ['1'] },
|
||||
subject: { operator: '~', values: ['foo'] },
|
||||
},
|
||||
]);
|
||||
|
||||
expect(Object.keys(map)).toEqual(['status', 'subject']);
|
||||
});
|
||||
|
||||
it('lets a later duplicate key overwrite an earlier one', () => {
|
||||
const map = ApiV3FilterBuilder.toFilterObject([
|
||||
{ status: { operator: '=', values: ['1'] } },
|
||||
{ status: { operator: '!', values: ['2'] } },
|
||||
]);
|
||||
|
||||
expect(map.status).toEqual({ operator: '!', values: ['2'] });
|
||||
});
|
||||
});
|
||||
|
||||
describe('round trips', () => {
|
||||
it('fromFilterObject reconstructs an equivalent builder', () => {
|
||||
const original = new ApiV3FilterBuilder()
|
||||
.add('status', '=', ['1'])
|
||||
.add('subject', '~', ['foo']);
|
||||
|
||||
const rebuilt = ApiV3FilterBuilder.fromFilterObject(
|
||||
ApiV3FilterBuilder.toFilterObject(original.filters),
|
||||
);
|
||||
|
||||
expect(rebuilt.filters).toEqual(original.filters);
|
||||
});
|
||||
|
||||
it('clone produces an independent copy', () => {
|
||||
const original = new ApiV3FilterBuilder().add('status', '=', ['1']);
|
||||
const copy = original.clone();
|
||||
copy.add('subject', '~', ['foo']);
|
||||
|
||||
expect(original.filters).toHaveLength(1);
|
||||
expect(copy.filters).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -83,7 +83,7 @@ export class ApiV3FilterBuilder {
|
||||
const map:ApiV3FilterObject = {};
|
||||
|
||||
filters.forEach((item:ApiV3Filter) => {
|
||||
_.each(item, (val:ApiV3FilterValue, filter:string) => {
|
||||
Object.entries(item).forEach(([filter, val]) => {
|
||||
map[filter] = val;
|
||||
});
|
||||
});
|
||||
@@ -129,7 +129,7 @@ export class ApiV3FilterBuilder {
|
||||
|
||||
public get filters():ApiV3Filter[] {
|
||||
const filters:ApiV3Filter[] = [];
|
||||
_.each(this.filterMap, (val:ApiV3FilterValue, filter:string) => {
|
||||
Object.entries(this.filterMap).forEach(([filter, val]) => {
|
||||
filters.push({ [filter]: val });
|
||||
});
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export class DragAndDropService implements OnDestroy {
|
||||
}
|
||||
|
||||
public member(container:HTMLElement):DragMember|undefined {
|
||||
return _.find(this.members, (el) => el.dragContainer === container);
|
||||
return this.members.find((el) => el.dragContainer === container);
|
||||
}
|
||||
|
||||
public get initialized() {
|
||||
|
||||
Reference in New Issue
Block a user