diff --git a/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts b/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts index e6299353dd..3b17b2aa88 100644 --- a/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts +++ b/src/app/child-dev-project/attendance/model/recurring-activity.spec.ts @@ -29,7 +29,5 @@ describe("RecurringActivity", () => { participants: ["1", "2"], linkedGroups: ["3"], excludedParticipants: ["5"], - - searchIndices: ["test", "activity"], }); }); diff --git a/src/app/child-dev-project/children/model/child.spec.ts b/src/app/child-dev-project/children/model/child.spec.ts index b7dc8237ec..ab4f350743 100644 --- a/src/app/child-dev-project/children/model/child.spec.ts +++ b/src/app/child-dev-project/children/model/child.spec.ts @@ -37,8 +37,6 @@ describe("Child", () => { dropoutDate: new Date("2022-03-31"), dropoutType: "unknown", dropoutRemarks: "no idea what happened", - - searchIndices: ["Max", "projectNumber01"], }); it("should determine isActive based on inferred state", () => { diff --git a/src/app/child-dev-project/children/model/child.ts b/src/app/child-dev-project/children/model/child.ts index 252752e61e..1f995fe6ad 100644 --- a/src/app/child-dev-project/children/model/child.ts +++ b/src/app/child-dev-project/children/model/child.ts @@ -57,6 +57,7 @@ export class Child extends Entity { @DatabaseField({ label: $localize`:Label for the project number of a child:Project Number`, labelShort: $localize`:Short label for the project number:PN`, + searchable: true, }) projectNumber: string; @@ -135,19 +136,4 @@ export class Child extends Entity { super.isActive ); } - - /** - * @override see {@link Entity} - */ - @DatabaseField() get searchIndices(): string[] { - let indices = []; - - indices = indices.concat(this.toString().split(" ")); - if (this.projectNumber !== undefined && this.projectNumber !== null) { - indices.push(this.projectNumber); - } - return indices; - } - - set searchIndices(value) {} } diff --git a/src/app/child-dev-project/schools/model/school.spec.ts b/src/app/child-dev-project/schools/model/school.spec.ts index 8625220fae..16a0d5eee4 100644 --- a/src/app/child-dev-project/schools/model/school.spec.ts +++ b/src/app/child-dev-project/schools/model/school.spec.ts @@ -22,6 +22,5 @@ describe("School Entity", () => { testEntitySubclass("School", School, { _id: "School:some-id", name: "Max", - searchIndices: ["Max"], }); }); diff --git a/src/app/core/entity/model/entity.spec.ts b/src/app/core/entity/model/entity.spec.ts index 111e5bff2a..0b5b499dd6 100644 --- a/src/app/core/entity/model/entity.spec.ts +++ b/src/app/core/entity/model/entity.spec.ts @@ -51,29 +51,6 @@ describe("Entity", () => { expect(data.otherText).toBeUndefined(); }); - it("rawData() includes searchIndices containing toString parts", function () { - const id = "test1"; - const entity = new Entity(id); - entity.toString = () => entity["name"]; - entity["name"] = "John Doe"; - - const data = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(data.searchIndices).toBeDefined(); - expect(data.searchIndices).toContain("John"); - expect(data.searchIndices).toContain("Doe"); - }); - - it("should not generate searchIndices for entities without a custom toString method", function () { - const id = "test1"; - const entity = new Entity(id); - entity["name"] = "John Doe"; - - const data = entitySchemaService.transformEntityToDatabaseFormat(entity); - - expect(data.searchIndices).toEqual([]); - }); - it("can perform a shallow copy of itself", () => { const id = "t1"; const entity: Entity = new Entity(id); @@ -176,9 +153,6 @@ export function testEntitySubclass( JSON.parse(JSON.stringify(expectedDatabaseFormat)) ); const rawData = schemaService.transformEntityToDatabaseFormat(entity); - if (rawData.searchIndices.length === 0) { - delete rawData.searchIndices; - } expect(rawData).toEqual(expectedDatabaseFormat); })); } diff --git a/src/app/core/entity/model/entity.ts b/src/app/core/entity/model/entity.ts index 54c7aa1aa2..fd4c301f0b 100644 --- a/src/app/core/entity/model/entity.ts +++ b/src/app/core/entity/model/entity.ts @@ -194,31 +194,6 @@ export class Entity { this._id = Entity.createPrefixedId(this.getType(), newEntityId); } - /** - * Returns an array of strings by which the entity can be searched. - * - * By default the parts of the string representation (toString) split at spaces is used if it is present. - * - * <b>Overwrite this method in subtypes if you want an entity type to be searchable by other properties.</b> - */ - @DatabaseField() get searchIndices(): string[] { - if ( - this.getConstructor().toStringAttributes === Entity.toStringAttributes && - this.toString() === this.entityId - ) { - // no indices for the default if an entity does not have a human-readable name - return []; - } - - // default indices generated from toString - return this.toString().split(" "); - } - - set searchIndices(value) { - // do nothing, always generated on the fly - // searchIndices is only saved to database so it can be used internally for database indexing - } - /** * Check, if this entity is considered active. * This is either taken from the property "inactive" (configured) or "active" (not configured). diff --git a/src/app/core/entity/schema/entity-schema-field.ts b/src/app/core/entity/schema/entity-schema-field.ts index a20db589e9..8d89dd72a1 100644 --- a/src/app/core/entity/schema/entity-schema-field.ts +++ b/src/app/core/entity/schema/entity-schema-field.ts @@ -47,6 +47,11 @@ export interface EntitySchemaField { */ generateIndex?: boolean; // TODO: implement index support in EntitySchema + /** + * If set to `true`, the entity can be found in the global search by entering this value + */ + searchable?: boolean; + /** * Whether the field should be initialized with a default value if undefined * (which is then run through dataType transformation); diff --git a/src/app/core/ui/search/search.component.spec.ts b/src/app/core/ui/search/search.component.spec.ts index 86dc05f18c..3676cec17f 100644 --- a/src/app/core/ui/search/search.component.spec.ts +++ b/src/app/core/ui/search/search.component.spec.ts @@ -7,14 +7,10 @@ import { } from "@angular/core/testing"; import { SearchComponent } from "./search.component"; -import { Child } from "../../../child-dev-project/children/model/child"; -import { School } from "../../../child-dev-project/schools/model/school"; import { DatabaseIndexingService } from "../../entity/database-indexing/database-indexing.service"; import { Subscription } from "rxjs"; -import { Entity } from "../../entity/model/entity"; import { MockedTestingModule } from "../../../utils/mocked-testing.module"; import { SwUpdate } from "@angular/service-worker"; -import { UserRoleGuard } from "../../permissions/permission-guard/user-role.guard"; describe("SearchComponent", () => { SearchComponent.INPUT_DEBOUNCE_TIME_MS = 4; @@ -36,7 +32,6 @@ describe("SearchComponent", () => { TestBed.configureTestingModule({ imports: [SearchComponent, MockedTestingModule.withState()], providers: [ - UserRoleGuard, { provide: DatabaseIndexingService, useValue: mockIndexService }, { provide: SwUpdate, useValue: {} }, ], @@ -69,11 +64,6 @@ describe("SearchComponent", () => { component.formControl.setValue("AB"); tick(SearchComponent.INPUT_DEBOUNCE_TIME_MS * 2); - expect(component.state).toBe(component.TOO_FEW_CHARACTERS); - expect(mockIndexService.queryIndexRaw).not.toHaveBeenCalled(); - - component.formControl.setValue("ABC"); - tick(SearchComponent.INPUT_DEBOUNCE_TIME_MS * 2); expect(component.state).toBe(component.NO_RESULTS); expect(mockIndexService.queryIndexRaw).toHaveBeenCalled(); @@ -97,58 +87,4 @@ describe("SearchComponent", () => { expectResultToBeEmpty(done); component.formControl.setValue(null); }); - - function expectResultToHave(queryResults: any, result: Entity, done: DoneFn) { - mockIndexService.queryIndexRaw.and.returnValue( - Promise.resolve(queryResults) - ); - - subscription = component.results.subscribe((next) => { - expect(next).toHaveSize(1); - expect(next[0]).toHaveId(result.getId()); - expect(mockIndexService.queryIndexRaw).toHaveBeenCalled(); - done(); - }); - } - - function generateDemoData(): [Child, School, object] { - const child1 = new Child("1"); - child1.name = "Adam X"; - const school1 = new School("s1"); - school1.name = "Anglo Primary"; - const mockQueryResults = { - rows: [ - { id: child1.getId(true), doc: { name: child1.name }, key: "adam" }, - { id: child1.getId(true), doc: { name: child1.name }, key: "x" }, - { id: school1.getId(true), doc: { name: school1.name }, key: "anglo" }, - { - id: school1.getId(true), - doc: { name: school1.name }, - key: "primary", - }, - ], - }; - return [child1, school1, mockQueryResults]; - } - - it("should set results correctly for search input", (done) => { - const [child1, , mockQueryResults] = generateDemoData(); - - expectResultToHave(mockQueryResults, child1, done); - component.formControl.setValue("Ada"); - }); - - it("should not include duplicates in results", (done) => { - const [child1, , mockQueryResults] = generateDemoData(); - - expectResultToHave(mockQueryResults, child1, done); - component.formControl.setValue("Ada"); - }); - - it("should only include results matching all search terms (words)", (done) => { - const [child1, , mockQueryResults] = generateDemoData(); - - expectResultToHave(mockQueryResults, child1, done); - component.formControl.setValue("A X"); - }); }); diff --git a/src/app/core/ui/search/search.component.ts b/src/app/core/ui/search/search.component.ts index 4d752f5404..d3a500ec20 100644 --- a/src/app/core/ui/search/search.component.ts +++ b/src/app/core/ui/search/search.component.ts @@ -1,12 +1,9 @@ import { Component, ViewEncapsulation } from "@angular/core"; import { Entity } from "../../entity/model/entity"; -import { from, Observable } from "rxjs"; -import { concatMap, debounceTime, skipUntil, tap } from "rxjs/operators"; -import { DatabaseIndexingService } from "../../entity/database-indexing/database-indexing.service"; +import { Observable } from "rxjs"; +import { concatMap, debounceTime, tap } from "rxjs/operators"; import { Router } from "@angular/router"; import { FormControl, ReactiveFormsModule } from "@angular/forms"; -import { EntitySchemaService } from "../../entity/schema/entity-schema.service"; -import { EntityRegistry } from "../../entity/database-entity.decorator"; import { UserRoleGuard } from "../../permissions/permission-guard/user-role.guard"; import { MatFormFieldModule } from "@angular/material/form-field"; import { FontAwesomeModule } from "@fortawesome/angular-fontawesome"; @@ -15,6 +12,7 @@ import { MatAutocompleteModule } from "@angular/material/autocomplete"; import { AsyncPipe, NgForOf, NgSwitch, NgSwitchCase } from "@angular/common"; import { DisplayEntityComponent } from "../../entity-components/entity-select/display-entity/display-entity.component"; import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; +import { SearchService } from "./search.service"; /** * General search box that provides results out of any kind of entities from the system @@ -43,8 +41,8 @@ import { UntilDestroy, untilDestroyed } from "@ngneat/until-destroy"; standalone: true, }) export class SearchComponent { - static INPUT_DEBOUNCE_TIME_MS: number = 400; - MIN_CHARACTERS_FOR_SEARCH: number = 3; + static INPUT_DEBOUNCE_TIME_MS = 400; + MIN_CHARACTERS_FOR_SEARCH = 2; readonly NOTHING_ENTERED = 0; readonly TOO_FEW_CHARACTERS = 1; @@ -60,15 +58,12 @@ export class SearchComponent { results: Observable<Entity[]>; constructor( - private indexingService: DatabaseIndexingService, private router: Router, private userRoleGuard: UserRoleGuard, - private entitySchemaService: EntitySchemaService, - private entities: EntityRegistry + private searchService: SearchService ) { this.results = this.formControl.valueChanges.pipe( debounceTime(SearchComponent.INPUT_DEBOUNCE_TIME_MS), - skipUntil(this.createSearchIndex()), tap((next) => (this.state = this.updateState(next))), concatMap((next: string) => this.searchResults(next)), untilDestroyed(this) @@ -94,19 +89,10 @@ export class SearchComponent { if (this.state !== this.SEARCH_IN_PROGRESS) { return []; } - const searchTerms = next.toLowerCase().split(" "); - const entities = await this.indexingService.queryIndexRaw( - "search_index/by_name", - { - startkey: searchTerms[0], - endkey: searchTerms[0] + "\ufff0", - include_docs: true, - } - ); - const filtered = this.prepareResults(entities.rows, searchTerms); - const uniques = this.uniquify(filtered); - this.state = uniques.length === 0 ? this.NO_RESULTS : this.SHOW_RESULTS; - return uniques; + const entities = await this.searchService.getSearchResults(next); + const filtered = this.prepareResults(entities); + this.state = filtered.length === 0 ? this.NO_RESULTS : this.SHOW_RESULTS; + return filtered; } async clickOption(optionElement) { @@ -126,68 +112,9 @@ export class SearchComponent { return /^[a-zA-Z]+|\d+$/.test(searchText); } - private createSearchIndex(): Observable<void> { - // `emit(x)` to add x as a key to the index that can be searched - const searchMapFunction = ` - (doc) => { - if (doc.hasOwnProperty("searchIndices")) { - doc.searchIndices.forEach(word => emit(word.toString().toLowerCase())); - } - }`; - - const designDoc = { - _id: "_design/search_index", - views: { - by_name: { - map: searchMapFunction, - }, - }, - }; - - // TODO move this to a service so it is not executed whenever a user logs in - return from(this.indexingService.createIndex(designDoc)); - } - - private prepareResults( - rows: [{ key: string; id: string; doc: object }], - searchTerms: string[] - ): Entity[] { - return rows - .map((doc) => this.transformDocToEntity(doc)) - .filter((entity) => - this.userRoleGuard.checkRoutePermissions(entity.getConstructor().route) - ) - .filter((entity) => - this.containsSecondarySearchTerms(entity, searchTerms) - ); - } - - private containsSecondarySearchTerms( - entity: Entity, - searchTerms: string[] - ): boolean { - const searchIndices = entity.searchIndices.join(" ").toLowerCase(); - return searchTerms.every((s) => searchIndices.includes(s)); - } - - private uniquify(entities: Entity[]): Entity[] { - const uniques = new Map<string, Entity>(); - entities.forEach((e) => { - uniques.set(e.getId(), e); - }); - return [...uniques.values()]; - } - - private transformDocToEntity(doc: { - key: string; - id: string; - doc: object; - }): Entity { - const ctor = this.entities.get(Entity.extractTypeFromId(doc.id)); - const entity = doc.id ? new ctor(doc.id) : new ctor(); - if (doc.doc) { - this.entitySchemaService.loadDataIntoEntity(entity, doc.doc); - } - return entity; + private prepareResults(entities: Entity[]): Entity[] { + return entities.filter((entity) => + this.userRoleGuard.checkRoutePermissions(entity.getConstructor().route) + ); } } diff --git a/src/app/core/ui/search/search.service.spec.ts b/src/app/core/ui/search/search.service.spec.ts new file mode 100644 index 0000000000..a41dc1f81a --- /dev/null +++ b/src/app/core/ui/search/search.service.spec.ts @@ -0,0 +1,112 @@ +import { TestBed } from "@angular/core/testing"; + +import { SearchService } from "./search.service"; +import { DatabaseTestingModule } from "../../../utils/database-testing.module"; +import { ChildSchoolRelation } from "../../../child-dev-project/children/model/childSchoolRelation"; +import { Child } from "../../../child-dev-project/children/model/child"; +import { EntityMapperService } from "../../entity/entity-mapper.service"; +import { Database } from "../../database/database"; + +describe("SearchService", () => { + let service: SearchService; + + beforeEach(() => { + TestBed.configureTestingModule({ imports: [DatabaseTestingModule] }); + }); + + afterEach(() => TestBed.inject(Database).destroy()); + + it("should allow to search for toStringAttributes that are not the entityId", async () => { + ChildSchoolRelation.toStringAttributes = ["entityId"]; + Child.toStringAttributes = ["name"]; + const c1 = Child.create("first"); + const c2 = Child.create("second"); + const r = new ChildSchoolRelation("relation"); + await TestBed.inject(EntityMapperService).saveAll([c1, c2, r]); + + service = TestBed.inject(SearchService); + + let res = await service.getSearchResults("firs"); + expect(res).toEqual([c1]); + res = await service.getSearchResults("relation"); + expect(res).toEqual([]); + }); + + it("should only index on database properties", async () => { + Child.toStringAttributes = ["schoolId", "name"]; + const child = Child.create("test"); + child.schoolId = "someSchool"; + await TestBed.inject(EntityMapperService).save(child); + + service = TestBed.inject(SearchService); + + let res = await service.getSearchResults("someSchool"); + expect(res).toEqual([]); + res = await service.getSearchResults("test"); + // reset default value + child.schoolId = ""; + expect(res).toEqual([child]); + }); + + it("should not fail if toStringAttribute is not set", async () => { + Child.toStringAttributes = ["projectNumber", "name"]; + const child = Child.create("test"); + await TestBed.inject(EntityMapperService).save(child); + + service = TestBed.inject(SearchService); + + const res = await service.getSearchResults("test"); + expect(res).toEqual([child]); + }); + + it("should include properties that are marked searchable", async () => { + Child.toStringAttributes = ["name"]; + Child.schema.get("projectNumber").searchable = true; + const child = Child.create("test"); + child.projectNumber = "number"; + await TestBed.inject(EntityMapperService).save(child); + + service = TestBed.inject(SearchService); + + let res = await service.getSearchResults("tes"); + expect(res).toEqual([child]); + res = await service.getSearchResults("numb"); + expect(res).toEqual([child]); + + delete Child.schema.get("projectNumber").searchable; + }); + + it("should support search terms with multiple words", async () => { + Child.toStringAttributes = ["name", "projectNumber"]; + const child = Child.create("test"); + child.projectNumber = "number"; + await TestBed.inject(EntityMapperService).save(child); + + service = TestBed.inject(SearchService); + + let res = await service.getSearchResults("tes num"); + expect(res).toEqual([child]); + }); + + it("should allows searches for properties with multiple words", async () => { + Child.toStringAttributes = ["name"]; + const child = Child.create("test name"); + await TestBed.inject(EntityMapperService).save(child); + + service = TestBed.inject(SearchService); + + let res = await service.getSearchResults("nam"); + expect(res).toEqual([child]); + }); + + it("should not return the same entity multiple times", async () => { + Child.toStringAttributes = ["name"]; + const child = Child.create("Peter Petersilie"); + await TestBed.inject(EntityMapperService).save(child); + + service = TestBed.inject(SearchService); + + let res = await service.getSearchResults("peter"); + expect(res).toEqual([child]); + }); +}); diff --git a/src/app/core/ui/search/search.service.ts b/src/app/core/ui/search/search.service.ts new file mode 100644 index 0000000000..97dd9c7ade --- /dev/null +++ b/src/app/core/ui/search/search.service.ts @@ -0,0 +1,118 @@ +import { Injectable } from "@angular/core"; +import { DatabaseIndexingService } from "../../entity/database-indexing/database-indexing.service"; +import { Entity } from "../../entity/model/entity"; +import { EntityRegistry } from "../../entity/database-entity.decorator"; +import { EntitySchemaService } from "../../entity/schema/entity-schema.service"; + +/** + * This service handles to logic for global searches across all entities + */ +@Injectable({ + providedIn: "root", +}) +export class SearchService { + private searchableEntities: [string, string[]][]; + + constructor( + private indexingService: DatabaseIndexingService, + private schemaService: EntitySchemaService, + private entities: EntityRegistry + ) { + this.createSearchIndex(); + } + + /** + * Creates the search index based on the `toStringAttributes` and the `searchable` schema property + * @private + */ + private createSearchIndex() { + this.initializeSearchableEntities(); + + const designDoc = { + _id: "_design/search_index", + views: { + by_name: { + map: this.getSearchIndexDesignDoc(), + }, + }, + }; + + this.indexingService.createIndex(designDoc); + } + + private initializeSearchableEntities() { + this.searchableEntities = [...this.entities.entries()] + .map(([name, ctr]) => { + const stringAttributes = ctr.toStringAttributes.filter((attr) => + ctr.schema.has(attr) + ); + const searchableAttributes = [...ctr.schema.entries()] + .filter(([_, schema]) => schema.searchable) + .map(([name]) => name); + return [name, [...stringAttributes, ...searchableAttributes]] as [ + string, + string[] + ]; + }) + .filter(([_, props]) => props.length > 0); + } + + private getSearchIndexDesignDoc() { + let searchIndex = `(doc) => {\n`; + this.searchableEntities.forEach(([type, attributes]) => { + searchIndex += `if (doc._id.startsWith("${type}:")) {\n`; + attributes.forEach((attr) => { + searchIndex += `if (doc["${attr}"]) {\n`; + searchIndex += `doc["${attr}"].toString().toLowerCase().split(" ").forEach((val) => emit(val))\n`; + searchIndex += `}\n`; + }); + searchIndex += `return\n`; + searchIndex += `}\n`; + }); + searchIndex += `}`; + return searchIndex; + } + + /** + * Returns the results matching the provided search term. + * Multiple search terms should be separated by a space + * @param searchTerm for which entities should be returned + */ + async getSearchResults(searchTerm: string): Promise<Entity[]> { + const searchTerms = searchTerm.toLowerCase().split(" "); + const res = await this.indexingService.queryIndexRaw( + "search_index/by_name", + { + startkey: searchTerms[0], + endkey: searchTerms[0] + "\ufff0", + include_docs: true, + } + ); + return this.getUniqueDocs(res.rows) + .filter((doc) => this.containsSecondarySearchTerms(doc, searchTerms)) + .map((doc) => this.transformDocToEntity(doc)); + } + + private getUniqueDocs(rows: any[]): any[] { + const uniques = new Map<string, any>(); + rows.forEach((row) => uniques.set(row.doc._id, row.doc)); + return [...uniques.values()]; + } + + private containsSecondarySearchTerms(doc, searchTerms: string[]): boolean { + const entityType = Entity.extractTypeFromId(doc._id); + const values = this.searchableEntities + .find(([type]) => type === entityType)[1] + .map((attr) => doc[attr]) + .join(" ") + .toLowerCase(); + return searchTerms.every((s) => values.includes(s)); + } + + private transformDocToEntity(doc): Entity { + const ctor = this.entities.get(Entity.extractTypeFromId(doc._id)); + const entity = new ctor(doc._id); + this.schemaService.loadDataIntoEntity(entity, doc); + return entity; + } +} diff --git a/src/app/core/user/user.spec.ts b/src/app/core/user/user.spec.ts index d08b719c91..8752f8efc2 100644 --- a/src/app/core/user/user.spec.ts +++ b/src/app/core/user/user.spec.ts @@ -24,8 +24,6 @@ describe("User", () => { name: "tester", paginatorSettingsPageSize: {}, - - searchIndices: ["tester"], }); it("should not allow to change the name after initialization and set it as the ID", () => { diff --git a/src/app/features/data-import/data-import.service.spec.ts b/src/app/features/data-import/data-import.service.spec.ts index 543f36d5e7..7b5a3d1b15 100644 --- a/src/app/features/data-import/data-import.service.spec.ts +++ b/src/app/features/data-import/data-import.service.spec.ts @@ -162,26 +162,6 @@ describe("DataImportService", () => { expect(test2.dateOfBirth).toBeDate("2011-06-07"); }); - it("should import csv file and generate searchIndices", async () => { - spyOn(db, "putAll"); - const data = [{ name: "John Doe", projectNumber: "123" }]; - const importMeta: ImportMetaData = { - entityType: "Child", - columnMap: { - name: { key: "name", label: "Name" }, - projectNumber: { key: "projectNumber", label: "Project number" }, - }, - }; - - await service.handleCsvImport(data, importMeta); - - expect(db.putAll).toHaveBeenCalledWith([ - jasmine.objectContaining({ - searchIndices: ["John", "Doe", "123"], - }), - ]); - }); - it("should save array strings as arrays", async () => { const data = [ { ID: "1", children: '["one", "two"]' }, diff --git a/src/app/features/data-import/data-import.service.ts b/src/app/features/data-import/data-import.service.ts index 1c982a771f..57a123996d 100644 --- a/src/app/features/data-import/data-import.service.ts +++ b/src/app/features/data-import/data-import.service.ts @@ -121,7 +121,6 @@ export class DataImportService { ): Promise<void> { const entities = data.map((row) => { const entity = this.createEntityWithRowData(row, importMeta); - this.createSearchIndices(importMeta, entity); if (!entity["_id"]) { entity["_id"] = `${importMeta.entityType}:${ importMeta.transactionId @@ -198,11 +197,6 @@ export class DataImportService { } } - private createSearchIndices(importMeta: ImportMetaData, entity) { - const ctor = this.entities.get(importMeta.entityType); - entity["searchIndices"] = Object.assign(new ctor(), entity).searchIndices; - } - private linkEntities(entities: any[], importMeta: ImportMetaData) { return this.linkableEntities[importMeta.entityType].find( ([type]) => type.ENTITY_TYPE === importMeta.linkEntity.type diff --git a/src/app/features/todos/model/todo.spec.ts b/src/app/features/todos/model/todo.spec.ts index b09301725b..079a65c273 100644 --- a/src/app/features/todos/model/todo.spec.ts +++ b/src/app/features/todos/model/todo.spec.ts @@ -11,8 +11,6 @@ describe("Todo", () => { description: "details of the task", assignedTo: ["demo"], relatedEntities: [], - - searchIndices: ["new", "task"], }); it("should infer isOverdue", () => { diff --git a/src/app/utils/expect-entity-data.spec.ts b/src/app/utils/expect-entity-data.spec.ts index d2bd0abb33..cc6c9907c5 100644 --- a/src/app/utils/expect-entity-data.spec.ts +++ b/src/app/utils/expect-entity-data.spec.ts @@ -156,12 +156,10 @@ function comparableEntityData(obj: any | any[], withoutId: boolean = false) { if (Array.isArray(obj)) { return obj.map((o) => comparableEntityData(o, withoutId)); } else { - const result = TestBed.inject( - EntitySchemaService - ).transformEntityToDatabaseFormat(obj); + const result = + TestBed.inject(EntitySchemaService).transformEntityToDatabaseFormat(obj); delete result._rev; - delete result.searchIndices; if (withoutId) { delete result._id; }