Skip to content

Commit 28500e1

Browse files
authored
Release 3.39.3
2 parents 39e9649 + 840cffd commit 28500e1

27 files changed

+453
-188
lines changed

src/app/app-initializers.ts

Lines changed: 0 additions & 72 deletions
This file was deleted.

src/app/app.module.ts

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,8 @@
1717

1818
import { BrowserModule } from "@angular/platform-browser";
1919
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
20-
import {
21-
APP_INITIALIZER,
22-
ErrorHandler,
23-
Inject,
24-
LOCALE_ID,
25-
NgModule,
26-
} from "@angular/core";
20+
import { LOCALE_ID, NgModule } from "@angular/core";
2721
import { HttpClientModule } from "@angular/common/http";
28-
import * as Sentry from "@sentry/angular";
2922

3023
import { AppComponent } from "./app.component";
3124
import { allRoutes } from "./app.routing";
@@ -40,8 +33,6 @@ import {
4033
import { environment } from "../environments/environment";
4134
import { AnalyticsService } from "./core/analytics/analytics.service";
4235
import { ConfigurableEnumModule } from "./core/basic-datatypes/configurable-enum/configurable-enum.module";
43-
import { MatPaginatorIntl } from "@angular/material/paginator";
44-
import { TranslatableMatPaginator } from "./core/language/TranslatableMatPaginator";
4536
import { FaIconLibrary } from "@fortawesome/angular-fontawesome";
4637
import { fas } from "@fortawesome/free-solid-svg-icons";
4738
import { far } from "@fortawesome/free-regular-svg-icons";
@@ -81,20 +72,20 @@ import { HistoricalDataModule } from "./features/historical-data/historical-data
8172
import { MatchingEntitiesModule } from "./features/matching-entities/matching-entities.module";
8273
import { ProgressDashboardWidgetModule } from "./features/dashboard-widgets/progress-dashboard-widget/progress-dashboard-widget.module";
8374
import { ReportingModule } from "./features/reporting/reporting.module";
84-
import { Router, RouterModule } from "@angular/router";
75+
import { RouterModule } from "@angular/router";
8576
import { TodosModule } from "./features/todos/todos.module";
86-
import moment from "moment";
87-
import { getLocaleFirstDayOfWeek } from "@angular/common";
8877
import { waitForChangeTo } from "./core/session/session-states/session-utils";
8978
import { LoginState } from "./core/session/session-states/login-state.enum";
90-
import { appInitializers } from "./app-initializers";
79+
import { APP_INITIALIZER_PROPAGATE_CONFIG_UPDATES } from "./core/config/config.app-initializer";
9180
import { ImportModule } from "./core/import/import.module";
9281
import { ShortcutDashboardWidgetModule } from "./features/dashboard-widgets/shortcut-dashboard-widget/shortcut-dashboard-widget.module";
9382
import { EntityCountDashboardWidgetModule } from "./features/dashboard-widgets/entity-count-dashboard-widget/entity-count-dashboard-widget.module";
9483
import { BirthdayDashboardWidgetModule } from "./features/dashboard-widgets/birthday-dashboard-widget/birthday-dashboard-widget.module";
9584
import { MarkdownPageModule } from "./features/markdown-page/markdown-page.module";
9685
import { LoginStateSubject } from "./core/session/session-type";
9786
import { AdminModule } from "./core/admin/admin.module";
87+
import { Logging } from "./core/logging/logging.service";
88+
import { APP_INITIALIZER_DEMO_DATA } from "./core/demo-data/demo-data.app-initializer";
9889

9990
/**
10091
* Main entry point of the application.
@@ -147,23 +138,7 @@ import { AdminModule } from "./core/admin/admin.module";
147138
MatDialogModule,
148139
],
149140
providers: [
150-
/* Sentry setup */
151-
{
152-
provide: ErrorHandler,
153-
useValue: Sentry.createErrorHandler(),
154-
},
155-
{
156-
provide: Sentry.TraceService,
157-
deps: [Router],
158-
},
159-
{
160-
provide: APP_INITIALIZER,
161-
useFactory: () => () => {},
162-
deps: [Sentry.TraceService],
163-
multi: true,
164-
},
165-
166-
{ provide: MatPaginatorIntl, useValue: TranslatableMatPaginator() },
141+
...Logging.getAngularTracingProviders(),
167142
{ provide: ComponentRegistry, useValue: componentRegistry },
168143
{ provide: EntityRegistry, useValue: entityRegistry },
169144
{ provide: WINDOW_TOKEN, useValue: window },
@@ -190,17 +165,13 @@ import { AdminModule } from "./core/admin/admin.module";
190165
}),
191166
deps: [LoginStateSubject],
192167
},
193-
appInitializers,
168+
APP_INITIALIZER_PROPAGATE_CONFIG_UPDATES,
169+
APP_INITIALIZER_DEMO_DATA,
194170
],
195171
bootstrap: [AppComponent],
196172
})
197173
export class AppModule {
198-
constructor(icons: FaIconLibrary, @Inject(LOCALE_ID) locale: string) {
174+
constructor(icons: FaIconLibrary) {
199175
icons.addIconPacks(fas, far);
200-
moment.updateLocale(moment.locale(), {
201-
week: {
202-
dow: getLocaleFirstDayOfWeek(locale),
203-
},
204-
});
205176
}
206177
}

src/app/core/analytics/analytics.service.spec.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import {
99
import { RouterTestingModule } from "@angular/router/testing";
1010
import { ConfigService } from "../config/config.service";
1111
import { UsageAnalyticsConfig } from "./usage-analytics-config";
12-
import { Subject } from "rxjs";
12+
import { BehaviorSubject, Subject } from "rxjs";
1313
import { Config } from "../config/config";
1414
import { SiteSettingsService } from "../site-settings/site-settings.service";
15+
import { LoginStateSubject } from "../session/session-type";
16+
import { SessionSubject } from "../session/auth/session-info";
17+
import { LoginState } from "../session/session-states/login-state.enum";
1518

1619
describe("AnalyticsService", () => {
1720
let service: AnalyticsService;
@@ -47,6 +50,11 @@ describe("AnalyticsService", () => {
4750
provide: SiteSettingsService,
4851
useValue: { siteName: siteNameSubject },
4952
},
53+
{
54+
provide: LoginStateSubject,
55+
useValue: new BehaviorSubject(LoginState.LOGGED_IN),
56+
},
57+
{ provide: SessionSubject, useValue: new BehaviorSubject(undefined) },
5058
],
5159
});
5260
service = TestBed.inject(AnalyticsService);

src/app/core/analytics/analytics.service.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import {
77
} from "./usage-analytics-config";
88
import { Angulartics2, Angulartics2Matomo } from "angulartics2";
99
import md5 from "md5";
10+
import { LoginState } from "../session/session-states/login-state.enum";
11+
import { LoginStateSubject } from "../session/session-type";
12+
import { SessionSubject } from "../session/auth/session-info";
1013

1114
/**
1215
* Track usage analytics data and report it to a backend server like Matomo.
@@ -23,7 +26,25 @@ export class AnalyticsService {
2326
private angulartics2: Angulartics2,
2427
private angulartics2Matomo: Angulartics2Matomo,
2528
private configService: ConfigService,
26-
) {}
29+
loginState: LoginStateSubject,
30+
private sessionInfo: SessionSubject,
31+
) {
32+
if (environment.production) {
33+
this.init();
34+
}
35+
36+
// update the user context for remote error logging and tracking and load config initially
37+
loginState.subscribe((s: LoginState) => this.updateSessionInfo(s));
38+
}
39+
40+
private updateSessionInfo(newState: LoginState): void {
41+
if (newState === LoginState.LOGGED_IN) {
42+
const username = this.sessionInfo.value?.name;
43+
this.setUser(username);
44+
} else {
45+
this.setUser(undefined);
46+
}
47+
}
2748

2849
/**
2950
* Sets a unique user hash which is always for the same user but does not expose the username.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { APP_INITIALIZER } from "@angular/core";
2+
import { ConfigService } from "./config.service";
3+
import { RouterService } from "./dynamic-routing/router.service";
4+
import { EntityConfigService } from "../entity/entity-config.service";
5+
import { Router } from "@angular/router";
6+
7+
export const APP_INITIALIZER_PROPAGATE_CONFIG_UPDATES = {
8+
provide: APP_INITIALIZER,
9+
useFactory:
10+
(
11+
configService: ConfigService,
12+
routerService: RouterService,
13+
entityConfigService: EntityConfigService,
14+
router: Router,
15+
) =>
16+
async () => {
17+
// Re-trigger services that depend on the config when something changes
18+
configService.configUpdates.subscribe(() => {
19+
routerService.initRouting();
20+
entityConfigService.setupEntitiesFromConfig();
21+
const url = location.href.replace(location.origin, "");
22+
router.navigateByUrl(url, { skipLocationChange: true });
23+
});
24+
},
25+
deps: [ConfigService, RouterService, EntityConfigService, Router],
26+
multi: true,
27+
};

src/app/core/database/sync.service.spec.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,25 @@ import { LoginStateSubject, SyncStateSubject } from "../session/session-type";
77
import { LoginState } from "../session/session-states/login-state.enum";
88
import { KeycloakAuthService } from "../session/auth/keycloak/keycloak-auth.service";
99
import { Subject } from "rxjs";
10+
import { NAVIGATOR_TOKEN } from "../../utils/di-tokens";
1011

1112
describe("SyncService", () => {
1213
let service: SyncService;
1314
let loginState: LoginStateSubject;
1415
let mockAuthService: jasmine.SpyObj<KeycloakAuthService>;
16+
let mockNavigator;
1517

1618
beforeEach(() => {
1719
mockAuthService = jasmine.createSpyObj(["login", "addAuthHeader"]);
20+
mockNavigator = { onLine: true };
21+
1822
TestBed.configureTestingModule({
1923
providers: [
2024
{ provide: KeycloakAuthService, useValue: mockAuthService },
2125
{ provide: Database, useClass: PouchDatabase },
2226
LoginStateSubject,
2327
SyncStateSubject,
28+
{ provide: NAVIGATOR_TOKEN, useValue: mockNavigator },
2429
],
2530
});
2631
service = TestBed.inject(SyncService);
@@ -94,4 +99,24 @@ describe("SyncService", () => {
9499

95100
stopPeriodicTimer();
96101
}));
102+
103+
it("should skip sync calls when offline", fakeAsync(() => {
104+
const mockLocalDb = jasmine.createSpyObj(["sync"]);
105+
mockLocalDb.sync.and.resolveTo({});
106+
const db = TestBed.inject(Database) as PouchDatabase;
107+
spyOn(db, "getPouchDB").and.returnValue(mockLocalDb);
108+
109+
mockNavigator.onLine = false;
110+
111+
service.startSync();
112+
113+
tick(1000);
114+
expect(mockLocalDb.sync).not.toHaveBeenCalled();
115+
116+
mockNavigator.onLine = true;
117+
tick(SyncService.SYNC_INTERVAL);
118+
expect(mockLocalDb.sync).toHaveBeenCalled();
119+
120+
stopPeriodicTimer();
121+
}));
97122
});

src/app/core/database/sync.service.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Injectable } from "@angular/core";
1+
import { Inject, Injectable } from "@angular/core";
22
import { Database } from "./database";
33
import { PouchDatabase } from "./pouch-database";
44
import { Logging } from "../logging/logging.service";
@@ -16,6 +16,7 @@ import { Config } from "../config/config";
1616
import { Entity } from "../entity/model/entity";
1717
import { from, interval, merge, of } from "rxjs";
1818
import { environment } from "../../../environments/environment";
19+
import { NAVIGATOR_TOKEN } from "../../utils/di-tokens";
1920

2021
/**
2122
* This service initializes the remote DB and manages the sync between the local and remote DB.
@@ -36,6 +37,7 @@ export class SyncService {
3637
private database: Database,
3738
private authService: KeycloakAuthService,
3839
private syncStateSubject: SyncStateSubject,
40+
@Inject(NAVIGATOR_TOKEN) private navigator: Navigator,
3941
) {
4042
this.remoteDatabase = new PouchDatabase(this.authService);
4143

@@ -89,6 +91,12 @@ export class SyncService {
8991
* Execute a (one-time) sync between the local and server database.
9092
*/
9193
sync(): Promise<SyncResult> {
94+
if (!this.navigator.onLine) {
95+
Logging.debug("Not syncing because offline");
96+
this.syncStateSubject.next(SyncState.UNSYNCED);
97+
return Promise.resolve({});
98+
}
99+
92100
this.syncStateSubject.next(SyncState.STARTED);
93101

94102
return this.localDB
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import {
2+
APP_INITIALIZER,
3+
Injector,
4+
ɵcreateInjector as createInjector,
5+
} from "@angular/core";
6+
import { environment } from "../../../environments/environment";
7+
8+
/**
9+
* Provide this in the app module to run the demo data generation with lazy-loading
10+
* (no download of module code if not in demo mode).
11+
*/
12+
export const APP_INITIALIZER_DEMO_DATA = {
13+
provide: APP_INITIALIZER,
14+
useFactory: (injector: Injector) => async () => {
15+
if (environment.demo_mode) {
16+
const m = await import("./demo-data.module");
17+
await createInjector(m.DemoDataModule, injector)
18+
.get(m.DemoDataModule)
19+
.publishDemoData();
20+
}
21+
},
22+
deps: [Injector],
23+
multi: true,
24+
};

src/app/core/entity/latest-entity-loader.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export abstract class LatestEntityLoader<T extends Entity> {
4848
} catch (err) {
4949
if (err?.status !== HttpStatusCode.NotFound) {
5050
Logging.error(
51-
`Loading entity "${this.entityCtor.ENTITY_TYPE}:${this.entityID}" failed: ${this.entityID}`,
51+
`Initial loading of entity "${this.entityCtor.ENTITY_TYPE}:${this.entityID}" failed [Service based on LatestEntityLoader]`,
5252
err,
5353
);
5454
}

0 commit comments

Comments
 (0)