Skip to content

Commit

Permalink
feat(#9488): show training cards once a day (#9513)
Browse files Browse the repository at this point in the history
  • Loading branch information
latin-panda authored Oct 14, 2024
1 parent b7b7d38 commit 40c0d24
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 16 deletions.
20 changes: 11 additions & 9 deletions tests/e2e/default/enketo/training-cards.wdio-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ describe('Training Cards', () => {

const formDocId = 'training:training-cards-text-only';

const setLastViewedDateInThePast = () => {
return browser.execute(function() {
this.localStorage.setItem('training-cards-last-viewed-date-user1', new Date('2024-10-05 20:10:05').toISOString());
});
};

before(async () => {
const parent = placeFactory.place().build({ _id: 'dist1', type: 'district_hospital' });
const user = userFactory.build({ roles: [ 'nurse', 'chw' ] });
Expand Down Expand Up @@ -52,9 +58,8 @@ describe('Training Cards', () => {
expect(await reportsPage.leftPanelSelectors.allReports()).to.be.empty;
});

it('should display training after it was canceled and the training doc was updated', async () => {
await commonPage.goToMessages();
await commonElements.waitForPageLoaded();
it('should not display training after it was canceled and the training doc was updated', async () => {
await setLastViewedDateInThePast();
// Unfinished trainings should appear again after reload.
await browser.refresh();
await trainingCardsPage.waitForTrainingCards();
Expand All @@ -73,19 +78,15 @@ describe('Training Cards', () => {
const updatedTrainingForm = await utils.getDoc(`form:${formDocId}`);
expect(updatedTrainingForm.context.duration).to.equal(10);

await trainingCardsPage.waitForTrainingCards();
const context = 'training_cards_text_only';
const introCard = await trainingCardsPage.getCardContent(context, 'intro/intro_note_1:label"]');
expect(introCard).to.equal(
'There have been some changes to icons in your app. The next few screens will show you the difference.'
);
await trainingCardsPage.checkTrainingCardIsNotDisplayed();
});

it('should display training after privacy policy', async () => {
const privacyPolicy = privacyPolicyFactory.privacyPolicy().build();
await utils.saveDocs([privacyPolicy]);
await commonPage.goToReports();
await commonElements.sync();
await setLastViewedDateInThePast();
await browser.refresh();

await trainingCardsPage.checkTrainingCardIsNotDisplayed();
Expand All @@ -97,6 +98,7 @@ describe('Training Cards', () => {
it('should display training after reload and complete training', async () => {
await commonPage.goToMessages();
await commonElements.waitForPageLoaded();
await setLastViewedDateInThePast();
// Unfinished trainings should appear again after reload.
await browser.refresh();
await trainingCardsPage.waitForTrainingCards();
Expand Down
5 changes: 1 addition & 4 deletions webapp/src/ts/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,7 @@ export class AppComponent implements OnInit, AfterViewInit {
}

private displayTrainingCards() {
if (this.showPrivacyPolicy && !this.privacyPolicyAccepted) {
return;
}
if (!this.trainingCardFormId) {
if (!this.trainingCardFormId || (this.showPrivacyPolicy && !this.privacyPolicyAccepted)) {
return;
}

Expand Down
50 changes: 47 additions & 3 deletions webapp/src/ts/services/training-cards.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import * as moment from 'moment';
import { v4 as uuid } from 'uuid';
import { first } from 'rxjs/operators';

import { XmlFormsService } from '@mm-services/xml-forms.service';
import { TrainingCardsComponent } from '@mm-modals/training-cards/training-cards.component';
Expand All @@ -17,7 +18,8 @@ export const TRAINING_PREFIX: string = 'training:';
providedIn: 'root'
})
export class TrainingCardsService {
private globalActions;
private readonly globalActions: GlobalActions;
private readonly STORAGE_KEY_LAST_VIEWED_DATE = 'training-cards-last-viewed-date';

constructor(
private store: Store,
Expand All @@ -27,7 +29,7 @@ export class TrainingCardsService {
private sessionService: SessionService,
private routeSnapshotService: RouteSnapshotService,
) {
this.globalActions = new GlobalActions(store);
this.globalActions = new GlobalActions(this.store);
}

private getAvailableTrainingCards(xForms, userCtx) {
Expand Down Expand Up @@ -98,11 +100,25 @@ export class TrainingCardsService {
}

displayTrainingCards() {
if (this.hasBeenDisplayed()) {
return;
}

const routeSnapshot = this.routeSnapshotService.get();
if (routeSnapshot?.data?.hideTraining) {
return;
}
this.modalService.show(TrainingCardsComponent);

this.modalService
.show(TrainingCardsComponent)
?.afterOpened()
.pipe(first())
.subscribe(() => {
const key = this.getLocalStorageKey();
if (key) {
window.localStorage.setItem(key, new Date().toISOString());
}
});
}

private async getFirstChronologicalForm(xForms) {
Expand Down Expand Up @@ -138,4 +154,32 @@ export class TrainingCardsService {
}
return `${TRAINING_PREFIX}${userName}:${uuid()}`;
}

private hasBeenDisplayed() {
const key = this.getLocalStorageKey();
if (!key) {
return false;
}

const dateString = window.localStorage.getItem(key) ?? '';
const lastViewedDate = new Date(dateString);
if (isNaN(lastViewedDate.getTime())) {
return false;
}

lastViewedDate.setHours(0, 0, 0, 0);
const today = new Date();
today.setHours(0, 0, 0, 0);

return lastViewedDate >= today;
}

private getLocalStorageKey() {
const username = this.sessionService.userCtx()?.name;
if (!username) {
return;
}

return `${this.STORAGE_KEY_LAST_VIEWED_DATE}-${username}`;
}
}
48 changes: 48 additions & 0 deletions webapp/tests/karma/ts/services/training-cards.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -771,4 +771,52 @@ describe('TrainingCardsService', () => {
.to.equal('Training Cards :: Cannot create document ID, user context does not have the "name" property.');
}
});

describe('Display training cards once', () => {
afterEach(() => window.localStorage.removeItem('training-cards-last-viewed-date'));

it('should display training when it has not been displayed today', async () => {
routeSnapshotService.get.returns({ data: { hideTraining: false } });
sessionService.userCtx.returns({ name: 'ronald' });
window.localStorage.setItem('training-cards-last-viewed-date-ronald', '2024-05-23 20:29:25');
clock = sinon.useFakeTimers(new Date('2025-05-25 20:29:25'));

service.displayTrainingCards();

expect(modalService.show.calledOnce).to.be.true;
});

it('should display training when last viewed date is empty', async () => {
routeSnapshotService.get.returns({ data: { hideTraining: false } });
window.localStorage.setItem('training-cards-last-viewed-date-ronald', '');
clock = sinon.useFakeTimers(new Date('2025-05-25 20:29:25'));

service.displayTrainingCards();

expect(modalService.show.calledOnce).to.be.true;
});

it('should not display training when it has been displayed today for the same user', async () => {
routeSnapshotService.get.returns({ data: { hideTraining: false } });
sessionService.userCtx.returns({ name: 'ronald' });
window.localStorage.setItem('training-cards-last-viewed-date-ronald', '2024-05-23 20:29:25');
clock = sinon.useFakeTimers(new Date('2024-05-23 06:29:25'));

service.displayTrainingCards();

expect(modalService.show.notCalled).to.be.true;
});

it('should display training when it has not been displayed for a different user', async () => {
routeSnapshotService.get.returns({ data: { hideTraining: false } });
sessionService.userCtx.returns({ name: 'sarah' });
window.localStorage.setItem('training-cards-last-viewed-date-ronald', '2024-05-23 20:29:25');
clock = sinon.useFakeTimers(new Date('2024-05-23 06:29:25'));

service.displayTrainingCards();

expect(modalService.show.calledOnce).to.be.true;
});
});

});

0 comments on commit 40c0d24

Please sign in to comment.