Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ng-http-caching request debouncing and caching #2171

Merged
merged 17 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ src/assets/old-client/
.vscode/chrome-userdatadir/

# Mac Junk
.DS_Store
.DS_Store
26 changes: 14 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-solid-svg-icons": "^6.1.1",
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
"@ngneat/cashew": "^3.0.0",
"@ngx-formly/bootstrap": "^6.2.2",
"@ngx-formly/core": "^6.2.2",
"@ngx-loading-bar/core": "^6.0.2",
Expand All @@ -71,6 +70,7 @@
"just-camel-case": "^6.0.1",
"just-snake-case": "^3.0.1",
"luxon": "^3.4.4",
"ng-http-caching": "^17.0.1",
"ngx-captcha": "^13.0.0",
"ngx-clipboard": "^16.0.0",
"ngx-cookie-service": "^17.0.1",
Expand Down
18 changes: 13 additions & 5 deletions src/app/app.server.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ import {
ServerModule,
ServerTransferStateModule,
} from "@angular/platform-server";
import { HttpCacheInterceptorModule } from "@ngneat/cashew";
import { BawTimeoutModule } from "@services/timeout/timeout.module";
import { UniversalDeviceDetectorService } from "@services/universal-device-detector/universal-device-detector.service";
import { DeviceDetectorService } from "ngx-device-detector";
import { environment } from "src/environments/environment";
import { NgHttpCachingConfig, NgHttpCachingModule, NgHttpCachingStrategy } from "ng-http-caching";
import { disableCache } from "@services/cache/ngHttpCachingConfig";
import { AppComponent } from "./app.component";
import { AppModule } from "./app.module";

// we disable caching on the server to prevent potentially serving stale data
// and data that requires authorization to unauthenticated users
export const serverCacheConfig = {
isCacheable: disableCache,
cacheStrategy: NgHttpCachingStrategy.DISALLOW_ALL,
} as const satisfies NgHttpCachingConfig;

@NgModule({
imports: [
Expand All @@ -19,14 +26,15 @@ import { AppModule } from "./app.module";
ServerTransferStateModule,
// Timeout API requests after set period
BawTimeoutModule.forRoot({ timeout: environment.ssrTimeout }),
// Cache API requests
HttpCacheInterceptorModule.forRoot({ strategy: "explicit" }),
// we explicitly provide NgHttpCachingModule with a disabled cache strategy
// to prevent caching on the server
NgHttpCachingModule.forRoot(serverCacheConfig),
],
providers: [
{
provide: DeviceDetectorService,
useClass: UniversalDeviceDetectorService
}
useClass: UniversalDeviceDetectorService,
},
],
bootstrap: [AppComponent],
})
Expand Down
53 changes: 53 additions & 0 deletions src/app/components/admin/settings/settings.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { createComponentFactory, Spectator } from "@ngneat/spectator";
import { MockBawApiModule } from "@baw-api/baw-apiMock.module";
import { SharedModule } from "@shared/shared.module";
import { assertPageInfo } from "@test/helpers/pageRoute";
import { CACHE_SETTINGS, CacheSettings } from "@services/cache/cache-settings";
import { AdminSettingsComponent } from "./settings.component";

describe("AdminSettingsComponent", () => {
let spectator: Spectator<AdminSettingsComponent>;

const createComponent = createComponentFactory({
component: AdminSettingsComponent,
imports: [MockBawApiModule, SharedModule],
providers: [
{ provide: CACHE_SETTINGS, useValue: new CacheSettings(true, false) },
],
});

const cacheEnabledInput = () =>
spectator.query<HTMLInputElement>("#enable-cache");
const cacheLoggingInput = () =>
spectator.query<HTMLInputElement>("#enable-cache-logging");

function cacheSettings(): CacheSettings {
return spectator.component.cacheSettings;
}

beforeEach(() => {
spectator = createComponent();
});

assertPageInfo(AdminSettingsComponent, "Client Settings");

it("should create", () => {
expect(spectator.component).toBeInstanceOf(AdminSettingsComponent);
});

it("should toggle the cache enabled state correctly", () => {
spectator.click(cacheEnabledInput());
expect(cacheSettings().enabled).toBeFalse();

spectator.click(cacheEnabledInput());
expect(cacheSettings().enabled).toBeTrue();
});

it("should toggle the cache logging correctly", () => {
spectator.click(cacheLoggingInput());
expect(cacheSettings().showLogging).toBeTrue();

spectator.click(cacheLoggingInput());
expect(cacheSettings().showLogging).toBeFalse();
});
});
hudson-newey marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 2 additions & 2 deletions src/app/components/admin/settings/settings.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { adminSettingsMenuItem } from "./settings.menus";
(change)="cacheSettings.setCaching($any($event.target).checked)"
/>
<label class="form-check-label" for="enable-cache">
Enable/Disable caching of API requests
Enable caching of API requests
</label>
</div>

Expand All @@ -41,7 +41,7 @@ import { adminSettingsMenuItem } from "./settings.menus";
(change)="cacheSettings.setLogging($any($event.target).checked)"
/>
<label class="form-check-label" for="enable-cache-logging">
Enable/Disable cache logging in the console
Enable cache logging in the console
</label>
</div>
`,
Expand Down
10 changes: 10 additions & 0 deletions src/app/helpers/unitConverters/unitConverters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Seconds = number;
type Milliseconds = number;

export function secondsToMilliseconds(seconds: Seconds): Milliseconds {
return seconds * 1_000;
}

export function millisecondsToSeconds(milliseconds: Milliseconds): Seconds {
return milliseconds / 1_000;
}
13 changes: 7 additions & 6 deletions src/app/models/AbstractModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@ export type AbstractModelConstructor<Model> = new (
*/
export abstract class AbstractModelWithoutId<Model = Record<string, any>> {
public constructor(raw: Model, protected injector?: AssociationInjector) {
this.getPersistentAttributes()
const transformedRaw = this.getPersistentAttributes()
.filter((attr) => attr.convertCase)
.forEach((attr) => {
.reduce((acc, attr) => {
const value = raw[attr.key];
raw[attr.key] = isInstantiated(value) ? camelCase(value) : value;
});
acc[attr.key] = isInstantiated(value) ? camelCase(value) : value;
return acc;
}, {});

return Object.assign(this, raw);
Object.assign(this, transformedRaw, raw);
}

/** Keys for accessing hidden data associated with a model */
Expand Down Expand Up @@ -235,7 +236,7 @@ export abstract class AbstractModelWithoutId<Model = Record<string, any>> {
// when a null value is present, we send the value in the json request
// when a File value is present, we send the value in the formData request
// The null/json scenario is used to support deleting images.
.filter((meta) => this[meta.key] instanceof(File) ? opts.formData : true)
.filter((meta) => this[meta.key] instanceof File ? opts.formData : true)
.filter((meta) =>
meta.supportedFormats.includes(opts.formData ? "formData" : "json")
)
Expand Down
10 changes: 10 additions & 0 deletions src/app/services/baw-api/api.interceptor.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
createHttpFactory,
HttpMethod,
SpectatorHttp,
SpyObject,
} from "@ngneat/spectator";
import { noop } from "rxjs";
import { generateUser } from "@test/fakes/User";
Expand All @@ -21,14 +22,17 @@ import {
} from "@helpers/custom-errors/baw-api-error";
import { generateBawApiError } from "@test/fakes/BawApiError";
import { NOT_FOUND, UNPROCESSABLE_ENTITY } from "http-status";
import { NgHttpCachingService } from "ng-http-caching";
import { BawSessionService } from "./baw-session.service";
import { shouldNotFail, shouldNotSucceed } from "./baw-api.service.spec";
import { CREDENTIALS_CONTEXT } from "./api.interceptor.service";

describe("BawApiInterceptor", () => {
let apiRoot: string;
let http: HttpClient;
let cachingSpy: SpyObject<NgHttpCachingService>;
let spec: SpectatorHttp<BawSessionService>;

const createService = createHttpFactory({
service: BawSessionService,
imports: [HttpClientModule, MockBawApiModule],
Expand All @@ -55,8 +59,14 @@ describe("BawApiInterceptor", () => {

beforeEach(() => {
spec = createService();

http = spec.inject(HttpClient);
apiRoot = spec.inject(API_ROOT);
cachingSpy = spec.inject(NgHttpCachingService);
});

afterEach(() => {
cachingSpy.clearCache();
});

describe("error handling", () => {
Expand Down
Loading
Loading