Skip to content

Commit

Permalink
refactor(config): define entity names and icons in entity config inst…
Browse files Browse the repository at this point in the history
…ead individually in views

closes #886
  • Loading branch information
sleidig committed Nov 10, 2022
1 parent b5e3d68 commit e9fbf69
Show file tree
Hide file tree
Showing 13 changed files with 99 additions and 65 deletions.
4 changes: 1 addition & 3 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ export class AppComponent {
}

private async initBasicServices() {
// TODO: remove this with issue #886
// This needs to be in the app module (as opposed to the dynamic entity service)
// to prevent circular dependencies
// TODO: remove this after issue #886 now in next release (keep as fallback for one version)
this.entities.add("Participant", Child);
this.entities.add("Team", School);

Expand Down
2 changes: 2 additions & 0 deletions src/app/child-dev-project/children/model/child.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ export type Center = ConfigurableEnumValue;
@DatabaseEntity("Child")
export class Child extends Entity {
static toStringAttributes = ["name"];
static icon = "child";
static label = "Participant";

static create(name: string): Child {
const instance = new Child();
Expand Down
1 change: 1 addition & 0 deletions src/app/child-dev-project/schools/model/school.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DatabaseField } from "../../../core/entity/database-field.decorator";
@DatabaseEntity("School")
export class School extends Entity {
static toStringAttributes = ["name"];
static icon = "university";

static getBlockComponent(): string {
return "SchoolBlock";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,12 @@ import { DynamicComponent } from "../../../core/view/dynamic-components/dynamic-
styleUrls: ["./school-block.component.scss"],
})
export class SchoolBlockComponent implements OnInitDynamicComponent, OnChanges {
icon: string;
icon: string = School.icon;
@Input() entity: School = new School("");
@Input() entityId: string;
@Input() linkDisabled: boolean;

constructor(
private entityMapper: EntityMapperService,
private configService: ConfigService
) {
this.icon =
this.configService.getConfig<ViewConfig>("view:school/:id")?.config?.icon;
}
constructor(private entityMapper: EntityMapperService) {}

ngOnChanges(changes: SimpleChanges) {
if (changes.hasOwnProperty("entityId")) {
Expand Down
8 changes: 4 additions & 4 deletions src/app/core/config/config-fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,6 @@ export const defaultJsonConfig = {
"component": "EntityDetails",
"config": {
"entity": "School",
"title": $localize`:Title when adding new entity|e.g. Add new School or Group:School or Group`,
"panels": [
{
"title": $localize`:Panel title:Basic Information`,
Expand Down Expand Up @@ -458,7 +457,6 @@ export const defaultJsonConfig = {
]
}
],
"icon": "university"
}
},
"view:child": {
Expand Down Expand Up @@ -603,7 +601,6 @@ export const defaultJsonConfig = {
"view:child/:id": {
"component": "EntityDetails",
"config": {
"icon": "child",
"entity": "Child",
"panels": [
{
Expand Down Expand Up @@ -813,7 +810,6 @@ export const defaultJsonConfig = {
]
}
],
"icon": "calendar-alt"
}
},
"view:report": {
Expand Down Expand Up @@ -940,6 +936,8 @@ export const defaultJsonConfig = {
},

"entity:Child": {
"label": "Participant",
"icon": "child",
"attributes": [
{
"name": "address",
Expand Down Expand Up @@ -980,6 +978,8 @@ export const defaultJsonConfig = {
]
},
"entity:School": {
"label": "School",
"icon": "university",
"attributes": [
{
"name": "name",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,11 @@ import { Entity } from "../../entity/model/entity";
* The configuration for a entity details page
*/
export interface EntityDetailsConfig {
/**
* The name of an icon which should be displayed next to the entity name.
* The name has to be a valid font-awesome icon name.
*/
icon: string;

/**
* The name of the entity (according to the ENTITY_TYPE).
*/
entity: string;

/**
* The label/title that is displayed in the user interface (if different than "entity")
*/
title?: string;

/**
* The configuration for the panels on this details page.
*/
Expand Down Expand Up @@ -66,6 +55,8 @@ export interface PanelComponent {
/**
* This interface represents the config which will be created by the entity-details component and passed to each of
* the panel components.
*
* This is not config that is defined and stored in the config file for customization.
*/
export interface PanelConfig {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*ngIf="creatingNew"
i18n="Title when adding a new entity|An entity is a child, note, school, etc."
>
Adding new {{ config?.title || config?.entity }}
Adding new {{ this.entityConstructor?.label }}
</app-view-title>

<button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ describe("EntityDetailsComponent", () => {
let routeObserver: Subscriber<any>;

const routeConfig: EntityDetailsConfig = {
icon: "child",
entity: "Child",
panels: [
{
Expand Down Expand Up @@ -68,33 +67,31 @@ describe("EntityDetailsComponent", () => {
let mockEntityRemoveService: jasmine.SpyObj<EntityRemoveService>;
let mockAbility: jasmine.SpyObj<EntityAbility>;

beforeEach(
waitForAsync(() => {
mockChildrenService = jasmine.createSpyObj([
"queryRelationsOf",
"getAserResultsOfChild",
]);
mockEntityRemoveService = jasmine.createSpyObj(["remove"]);
mockChildrenService.queryRelationsOf.and.resolveTo([]);
mockChildrenService.getAserResultsOfChild.and.returnValue(of([]));
mockAbility = jasmine.createSpyObj(["cannot", "update"]);
mockAbility.cannot.and.returnValue(false);
TestBed.configureTestingModule({
imports: [
ChildrenModule,
MockedTestingModule.withState(),
FontAwesomeTestingModule,
TabStateModule,
],
providers: [
{ provide: ActivatedRoute, useValue: mockedRoute },
{ provide: ChildrenService, useValue: mockChildrenService },
{ provide: EntityRemoveService, useValue: mockEntityRemoveService },
{ provide: EntityAbility, useValue: mockAbility },
],
}).compileComponents();
})
);
beforeEach(waitForAsync(() => {
mockChildrenService = jasmine.createSpyObj([
"queryRelationsOf",
"getAserResultsOfChild",
]);
mockEntityRemoveService = jasmine.createSpyObj(["remove"]);
mockChildrenService.queryRelationsOf.and.resolveTo([]);
mockChildrenService.getAserResultsOfChild.and.returnValue(of([]));
mockAbility = jasmine.createSpyObj(["cannot", "update"]);
mockAbility.cannot.and.returnValue(false);
TestBed.configureTestingModule({
imports: [
ChildrenModule,
MockedTestingModule.withState(),
FontAwesomeTestingModule,
TabStateModule,
],
providers: [
{ provide: ActivatedRoute, useValue: mockedRoute },
{ provide: ChildrenService, useValue: mockChildrenService },
{ provide: EntityRemoveService, useValue: mockEntityRemoveService },
{ provide: EntityAbility, useValue: mockAbility },
],
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(EntityDetailsComponent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
PanelComponent,
PanelConfig,
} from "./EntityDetailsConfig";
import { Entity } from "../../entity/model/entity";
import { Entity, EntityConstructor } from "../../entity/model/entity";
import { EntityMapperService } from "../../entity/entity-mapper.service";
import { getUrlWithoutParams } from "../../../utils/utils";
import { RouteData } from "../../view/dynamic-routing/view-config.interface";
Expand Down Expand Up @@ -37,8 +37,8 @@ export class EntityDetailsComponent {
isLoading = true;

panels: Panel[] = [];
iconName: string;
config: EntityDetailsConfig;
entityConstructor: EntityConstructor;

constructor(
private entityMapperService: EntityMapperService,
Expand All @@ -51,30 +51,31 @@ export class EntityDetailsComponent {
) {
this.route.data.subscribe((data: RouteData<EntityDetailsConfig>) => {
this.config = data.config;
this.entityConstructor = this.entities.get(this.config.entity);
this.setInitialPanelsConfig();
this.iconName = data.config.icon;
this.route.paramMap.subscribe((params) =>
this.loadEntity(params.get("id"))
);
});
}

private loadEntity(id: string) {
const constr = this.entities.get(this.config.entity);
if (id === "new") {
if (this.ability.cannot("create", constr)) {
if (this.ability.cannot("create", this.entityConstructor)) {
this.router.navigate([""]);
return;
}
this.entity = new constr();
this.entity = new this.entityConstructor();
this.creatingNew = true;
this.setFullPanelsConfig();
} else {
this.creatingNew = false;
this.entityMapperService.load<Entity>(constr, id).then((entity) => {
this.entity = entity;
this.setFullPanelsConfig();
});
this.entityMapperService
.load<Entity>(this.entityConstructor, id)
.then((entity) => {
this.entity = entity;
this.setFullPanelsConfig();
});
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/app/core/entity/entity-config.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,16 @@ describe("EntityConfigService", () => {
test.name = "testName";
expect(test.toString()).toBe("testName id");
});

it("should allow to configure the label and icon for entity", () => {
mockConfigService.getAllConfigs.and.returnValue([
{ _id: "entity:Test", label: "test", icon: "users" },
]);
service.setupEntitiesFromConfig();

expect(Test.label).toBe("test");
expect(Test.icon).toBe("users");
});
});

@DatabaseEntity("Test")
Expand Down
16 changes: 16 additions & 0 deletions src/app/core/entity/entity-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ export class EntityConfigService {
if (entityConfig?.toStringAttributes) {
entityType.toStringAttributes = entityConfig.toStringAttributes;
}
if (entityConfig?.label) {
entityType.label = entityConfig.label;
}
if (entityConfig?.icon) {
entityType.icon = entityConfig.icon;
}
}

/**
Expand Down Expand Up @@ -105,4 +111,14 @@ export interface EntityConfig {
* (optional) the default is the ID of the entity (`.entityId`)
*/
toStringAttributes?: string[];

/**
* human-readable name/label of the entity in the UI
*/
label?: string;

/**
* icon used to visualize the entity type
*/
icon?: string;
}
8 changes: 8 additions & 0 deletions src/app/core/entity/model/entity.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { EntitySchemaService } from "../schema/entity-schema.service";
import { DatabaseField } from "../database-field.decorator";
import { ConfigurableEnumDatatype } from "../../configurable-enum/configurable-enum-datatype/configurable-enum-datatype";
import { createTestingConfigService } from "../../config/config.service";
import { DatabaseEntity } from "../database-entity.decorator";

describe("Entity", () => {
let entitySchemaService: EntitySchemaService;
Expand Down Expand Up @@ -81,6 +82,13 @@ describe("Entity", () => {
expect(otherEntity).toEqual(entity);
expect(otherEntity).toBeInstanceOf(TestEntity);
});

it("should use entity type as default label if none is explicitly configured", () => {
@DatabaseEntity("TestEntityForLabel")
class TestEntity extends Entity {}

expect(TestEntity.label).toBe("TestEntityForLabel");
});
});

export function testEntitySubclass(
Expand Down
16 changes: 16 additions & 0 deletions src/app/core/entity/model/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ export class Entity {
*/
static toStringAttributes = ["entityId"];

/**
* human-readable name/label of the entity in the UI
*/
static get label(): string {
return this._label ?? this.ENTITY_TYPE;
}
static set label(value: string) {
this._label = value;
}
private static _label: string;

/**
* icon id used for this entity
*/
static icon: string;

/**
* Extract the ENTITY_TYPE from an id.
* @param id An entity's id including prefix.
Expand Down

0 comments on commit e9fbf69

Please sign in to comment.