Skip to content

Commit 4499388

Browse files
committed
feat: add ability to merge config files
1 parent 10811f8 commit 4499388

File tree

3 files changed

+101
-4
lines changed

3 files changed

+101
-4
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,8 @@ Thumbs.db
5656
.angular
5757

5858
# karma
59-
karma-junit.xml
59+
karma-junit.xml
60+
61+
# config files overrides
62+
config.override.json
63+
config.override.*.json

src/app/app-config.service.spec.ts

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
AppConfigService,
66
HelpMessages,
77
} from "app-config.service";
8-
import { of } from "rxjs";
8+
import { Observable, of } from "rxjs";
99
import { MockHttp } from "shared/MockStubs";
1010

1111
const appConfig: AppConfigInterface = {
@@ -280,6 +280,47 @@ describe("AppConfigService", () => {
280280
});
281281

282282
describe("#getConfig()", () => {
283+
const mockConfigResponses: Record<string, object> = {
284+
"/assets/config.json": {
285+
accessTokenPrefix: "",
286+
lbBaseURL: "http://127.0.0.1:3000",
287+
gettingStarted: null,
288+
mainMenu: { nonAuthenticatedUser: { datasets: true } },
289+
},
290+
"/assets/config.0.json": { accessTokenPrefix: "Bearer " },
291+
"/assets/config.override.json": {
292+
gettingStarted: "aGettingStarted",
293+
addDatasetEnabled: true,
294+
mainMenu: { nonAuthenticatedUser: { files: true } },
295+
},
296+
"/assets/config.override.0.json": { siteTitle: "Test title" },
297+
};
298+
299+
const mergedConfig = {
300+
accessTokenPrefix: "Bearer ",
301+
lbBaseURL: "http://127.0.0.1:3000",
302+
gettingStarted: "aGettingStarted",
303+
addDatasetEnabled: true,
304+
siteTitle: "Test title",
305+
mainMenu: { nonAuthenticatedUser: { datasets: true, files: true } },
306+
};
307+
308+
const mockHttpGet = (backendError = false) => {
309+
spyOn(service["http"], "get").and.callFake(
310+
(url: string): Observable<any> => {
311+
if (url === "/api/v3/admin/config") {
312+
if (backendError) {
313+
return new Observable((sub) =>
314+
sub.error(new Error("No config in backend")),
315+
);
316+
}
317+
return of(mergedConfig);
318+
}
319+
return of(mockConfigResponses[url] || {});
320+
},
321+
);
322+
};
323+
283324
it("should return the AppConfig object", async () => {
284325
spyOn(service["http"], "get").and.returnValue(of(appConfig));
285326
await service.loadAppConfig();
@@ -288,5 +329,29 @@ describe("AppConfigService", () => {
288329

289330
expect(config).toEqual(appConfig);
290331
});
332+
333+
it("should merge multiple config JSONs", async () => {
334+
mockHttpGet();
335+
const config = await service["mergeConfig"]();
336+
expect(config).toEqual(mergedConfig);
337+
});
338+
339+
it("should return the merged appConfig", async () => {
340+
mockHttpGet(true);
341+
await service.loadAppConfig();
342+
343+
expect(service["appConfig"]).toEqual(
344+
jasmine.objectContaining(mergedConfig),
345+
);
346+
expect(service["http"].get).toHaveBeenCalledWith("/api/v3/admin/config");
347+
expect(service["http"].get).toHaveBeenCalledWith("/assets/config.json");
348+
expect(service["http"].get).toHaveBeenCalledWith("/assets/config.0.json");
349+
expect(service["http"].get).toHaveBeenCalledWith(
350+
"/assets/config.override.json",
351+
);
352+
expect(service["http"].get).toHaveBeenCalledWith(
353+
"/assets/config.override.0.json",
354+
);
355+
});
291356
});
292357
});

src/app/app-config.service.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { HttpClient } from "@angular/common/http";
22
import { Injectable } from "@angular/core";
3-
import { timeout } from "rxjs/operators";
3+
import { merge } from "lodash-es";
4+
import { firstValueFrom, forkJoin, of } from "rxjs";
5+
import { catchError, timeout } from "rxjs/operators";
46
import {
57
DatasetDetailComponentConfig,
68
DatasetsListSettings,
@@ -156,9 +158,35 @@ function isMainPageConfiguration(obj: any): obj is MainPageConfiguration {
156158
})
157159
export class AppConfigService {
158160
private appConfig: object = {};
161+
private configsSize = 4;
159162

160163
constructor(private http: HttpClient) {}
161164

165+
private async loadAndMerge(files: string[]): Promise<object> {
166+
const requests = files.map((f) =>
167+
this.http.get(`/assets/${f}`).pipe(catchError(() => of({}))),
168+
);
169+
const configs = await firstValueFrom(forkJoin(requests));
170+
return configs.reduce((acc, cfg) => merge(acc, cfg), {});
171+
}
172+
173+
private async mergeConfig(): Promise<object> {
174+
const normalConfigFiles = Array.from(
175+
{ length: this.configsSize },
176+
(_, i) => `config.${i}.json`,
177+
);
178+
const overrideConfigFiles = Array.from(
179+
{ length: this.configsSize },
180+
(_, i) => `config.override.${i}.json`,
181+
);
182+
return await this.loadAndMerge([
183+
"config.json",
184+
...normalConfigFiles,
185+
"config.override.json",
186+
...overrideConfigFiles,
187+
]);
188+
}
189+
162190
async loadAppConfig(): Promise<void> {
163191
try {
164192
const config = await this.http
@@ -169,7 +197,7 @@ export class AppConfigService {
169197
} catch (err) {
170198
console.log("No config available in backend, trying with local config.");
171199
try {
172-
const config = await this.http.get("/assets/config.json").toPromise();
200+
const config = await this.mergeConfig();
173201
this.appConfig = Object.assign({}, this.appConfig, config);
174202
} catch (err) {
175203
console.error("No config provided.");

0 commit comments

Comments
 (0)