Skip to content

Commit

Permalink
Merge pull request #6469 from NMDSdevopsServiceAdm/feat/1589-data-qua…
Browse files Browse the repository at this point in the history
…lity-log-in-message

Feat/1589 data quality log in message
  • Loading branch information
duncanc19 authored Jan 13, 2025
2 parents 3cfd4ef + 5989fc2 commit 1333c67
Show file tree
Hide file tree
Showing 16 changed files with 495 additions and 139 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
'use strict';

const userTable = { tableName: 'User', schema: 'cqc' };

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
return queryInterface.addColumn(userTable, 'LastViewedVacanciesAndTurnoverMessage', {
type: Sequelize.DataTypes.DATE,
allowNull: true,
});
},

async down(queryInterface) {
return queryInterface.removeColumn(userTable, 'LastViewedVacanciesAndTurnoverMessage');
},
};
16 changes: 16 additions & 0 deletions backend/server/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,11 @@ module.exports = function (sequelize, DataTypes) {
allowNull: true,
field: '"CanManageWdfClaimsChangedBy"',
},
lastViewedVacanciesAndTurnoverMessage: {
type: DataTypes.DATE,
allowNull: true,
field: 'LastViewedVacanciesAndTurnoverMessage',
},
},
{
tableName: '"User"',
Expand Down Expand Up @@ -542,5 +547,16 @@ module.exports = function (sequelize, DataTypes) {
return userFound;
};

User.setDateForLastViewedVacanciesAndTurnoverMessage = async function (userUid) {
return await this.update(
{ lastViewedVacanciesAndTurnoverMessage: new Date() },
{
where: {
uid: userUid,
},
},
);
};

return User;
};
23 changes: 22 additions & 1 deletion backend/server/routes/accounts/user.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// default route for user endpoint
const express = require('express');
const router = express.Router();
const { validate } = require('uuid');

const models = require('../../models');
const Authorization = require('../../utils/security/isAuthenticated');
Expand Down Expand Up @@ -809,7 +810,7 @@ router.route('/swap/establishment/notification/:nmsdId').get(async (req, res) =>
let notificationArr = [];

const establishmentNotifications = await notifications.selectNotificationByEstablishment(req.establishmentUid);
if(establishmentNotifications) notificationArr.push(establishmentNotifications);
if (establishmentNotifications) notificationArr.push(establishmentNotifications);
return res.status(200).send(notificationArr);
}
} catch (e) {
Expand Down Expand Up @@ -911,6 +912,22 @@ const swapEstablishment = async (req, res) => {
.json(response);
};

const updateLastViewedVacanciesAndTurnoverMessage = async (req, res) => {
try {
const userUid = req.params?.userUid;

if (!validate(userUid)) {
return res.status(400).send('User UID invalid');
}

await models.user.setDateForLastViewedVacanciesAndTurnoverMessage(userUid);

return res.status(200).send('Last viewed date updated');
} catch (error) {
return res.status(500).send('Failed to update last viewed date');
}
};

router.route('/').get(return200);
router.route('/admin').get(Authorization.isAdmin, listAdminUsers);
router.route('/admin/:userId').get(Authorization.isAdmin, getUser);
Expand Down Expand Up @@ -945,6 +962,9 @@ router.route('/my/establishments').get(Authorization.isAuthorised, listEstablish
router.route('/admin/:userId').get(Authorization.isAuthorised, getUser);
router.use('/my/notifications', Authorization.isAuthorised);
router.route('/my/notifications').get(listNotifications);
router
.route('/update-last-viewed-vacancies-and-turnover-message/:userUid')
.post(Authorization.isAuthorised, updateLastViewedVacanciesAndTurnoverMessage);

router.use('/swap/establishment/:id', authLimiter);
router.route('/swap/establishment/:id').post(Authorization.isAdmin, swapEstablishment);
Expand All @@ -955,3 +975,4 @@ module.exports.partAddUser = partAddUser;

module.exports.listAdminUsers = listAdminUsers;
module.exports.updateUser = updateUser;
module.exports.updateLastViewedVacanciesAndTurnoverMessage = updateLastViewedVacanciesAndTurnoverMessage;
3 changes: 3 additions & 0 deletions backend/server/routes/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ router.post('/', async (req, res) => {
'UserRoleValue',
'registrationSurveyCompleted',
'tribalId',
'lastViewedVacanciesAndTurnoverMessage',
],
include: [
{
Expand All @@ -95,6 +96,7 @@ router.post('/', async (req, res) => {
'lastBulkUploaded',
'eightWeeksFromFirstLogin',
'EmployerTypeValue',
'dataOwner',
],
include: [
{
Expand Down Expand Up @@ -284,6 +286,7 @@ router.post('/', async (req, res) => {
migratedUser,
},
establishmentUser.user.registrationSurveyCompleted,
establishmentUser.user.lastViewedVacanciesAndTurnoverMessage,
);

await models.sequelize.transaction(async (t) => {
Expand Down
49 changes: 48 additions & 1 deletion backend/server/test/unit/routes/accounts/user.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ const expect = chai.expect;
const sinon = require('sinon');
const httpMocks = require('node-mocks-http');

const { meetsMaxUserLimit, partAddUser, listAdminUsers, updateUser } = require('../../../../routes/accounts/user');
const {
meetsMaxUserLimit,
partAddUser,
listAdminUsers,
updateUser,
updateLastViewedVacanciesAndTurnoverMessage,
} = require('../../../../routes/accounts/user');
const User = require('../../../../models/classes/user').User;
const models = require('../../../../models');

describe('user.js', () => {
let req;
Expand Down Expand Up @@ -345,4 +352,44 @@ describe('user.js', () => {
});
});
});

describe('updateLastViewedVacanciesAndTurnoverMessage', () => {
let req;
let res;

beforeEach(() => {
req = httpMocks.createRequest();
res = httpMocks.createResponse();
});

it('should return 200 response if userUid in params is valid and database call successful', async () => {
req.params = { userUid: '6b6885fa-340d-4d59-8720-c03d8845e603' };
sinon.stub(models.user, 'setDateForLastViewedVacanciesAndTurnoverMessage').returns(null);

await updateLastViewedVacanciesAndTurnoverMessage(req, res);

expect(res.statusCode).to.equal(200);
expect(res._getData()).to.deep.equal('Last viewed date updated');
});

it('should return 400 response if userUid in params invalid', async () => {
req.params = { userUid: 'invalid-uid' };
sinon.stub(models.user, 'setDateForLastViewedVacanciesAndTurnoverMessage').returns(null);

await updateLastViewedVacanciesAndTurnoverMessage(req, res);

expect(res.statusCode).to.equal(400);
expect(res._getData()).to.deep.equal('User UID invalid');
});

it('should return 500 response if unexpected error', async () => {
req.params = { userUid: '6b6885fa-340d-4d59-8720-c03d8845e603' };
sinon.stub(models.user, 'setDateForLastViewedVacanciesAndTurnoverMessage').throws();

await updateLastViewedVacanciesAndTurnoverMessage(req, res);

expect(res.statusCode).to.equal(500);
expect(res._getData()).to.deep.equal('Failed to update last viewed date');
});
});
});
3 changes: 3 additions & 0 deletions backend/server/utils/login/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = (
agreedUpdatedTerms,
migratedUser,
registrationSurveyCompleted,
lastViewedVacanciesAndTurnoverMessage,
) => {
// note - the mainService can be null
return {
Expand All @@ -35,6 +36,7 @@ module.exports = (
parentName: establishment.parentName ? establishment.parentName : undefined,
isFirstBulkUpload: establishment.lastBulkUploaded ? false : true,
employerTypeSet: establishment.EmployerTypeValue ? true : false,
dataOwner: establishment.dataOwner,
}
: null,
mainService: establishment
Expand All @@ -45,5 +47,6 @@ module.exports = (
: null,
expiryDate: expiryDate,
registrationSurveyCompleted: registrationSurveyCompleted,
lastViewedVacanciesAndTurnoverMessage,
};
};
8 changes: 7 additions & 1 deletion frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ import { FirstLoginPageComponent } from '@features/first-login-page/first-login-
import { ForgotYourPasswordComponent } from '@features/forgot-your-username-or-password/forgot-your-password/forgot-your-password.component';
import { ForgotYourUsernameOrPasswordComponent } from '@features/forgot-your-username-or-password/forgot-your-username-or-password.component';
import { ForgotYourUsernameComponent } from '@features/forgot-your-username-or-password/forgot-your-username/forgot-your-username.component';
import { UsernameFoundComponent } from '@features/forgot-your-username-or-password/username-found/username-found.component';
import { SecurityQuestionAnswerNotMatchComponent } from '@features/forgot-your-username-or-password/forgot-your-username/security-question-answer-not-match/security-question-answer-not-match.component';
import { UserAccountNotFoundComponent } from '@features/forgot-your-username-or-password/forgot-your-username/user-account-not-found/user-account-not-found.component';
import { UsernameFoundComponent } from '@features/forgot-your-username-or-password/username-found/username-found.component';
import { LoginComponent } from '@features/login/login.component';
import { VacanciesAndTurnoverLoginMessage } from '@features/login/vacancies-and-turnover-login-message/vacancies-and-turnover-login-message.component';
import { LogoutComponent } from '@features/logout/logout.component';
import { MigratedUserTermsConditionsComponent } from '@features/migrated-user-terms-conditions/migrated-user-terms-conditions.component';
import { BecomeAParentComponent } from '@features/new-dashboard/become-a-parent/become-a-parent.component';
Expand Down Expand Up @@ -148,6 +149,11 @@ const routes: Routes = [
component: MigratedUserTermsConditionsComponent,
data: { title: 'Migrated User Terms And Conditions' },
},
{
path: 'update-your-vacancies-and-turnover-data',
component: VacanciesAndTurnoverLoginMessage,
data: { title: 'Update your vacancies and turnover data' },
},
{
path: 'workplace',
loadChildren: () => import('@features/workplace/workplace.module').then((m) => m.WorkplaceModule),
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { Angulartics2Module } from 'angulartics2';
import { HighchartsChartModule } from 'highcharts-angular';

import { CommonModule } from '@angular/common';
import { HTTP_INTERCEPTORS, HttpClient, HttpClientModule } from '@angular/common/http';
import { ErrorHandler, NgModule } from '@angular/core';
Expand Down Expand Up @@ -72,7 +69,9 @@ import { FindUsernameComponent } from '@features/forgot-your-username-or-passwor
import { ForgotYourUsernameComponent } from '@features/forgot-your-username-or-password/forgot-your-username/forgot-your-username.component';
import { SecurityQuestionAnswerNotMatchComponent } from '@features/forgot-your-username-or-password/forgot-your-username/security-question-answer-not-match/security-question-answer-not-match.component';
import { UserAccountNotFoundComponent } from '@features/forgot-your-username-or-password/forgot-your-username/user-account-not-found/user-account-not-found.component';
import { UsernameFoundComponent } from '@features/forgot-your-username-or-password/username-found/username-found.component';
import { LoginComponent } from '@features/login/login.component';
import { VacanciesAndTurnoverLoginMessage } from '@features/login/vacancies-and-turnover-login-message/vacancies-and-turnover-login-message.component';
import { LogoutComponent } from '@features/logout/logout.component';
import { BecomeAParentComponent } from '@features/new-dashboard/become-a-parent/become-a-parent.component';
import { DashboardWrapperComponent } from '@features/new-dashboard/dashboard-wrapper.component';
Expand All @@ -94,14 +93,15 @@ import { BenchmarksModule } from '@shared/components/benchmarks-tab/benchmarks.m
import { DataAreaTabModule } from '@shared/components/data-area-tab/data-area-tab.module';
import { FeatureFlagsService } from '@shared/services/feature-flags.service';
import { SharedModule } from '@shared/shared.module';
import { Angulartics2Module } from 'angulartics2';
import { HighchartsChartModule } from 'highcharts-angular';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StaffMismatchBannerComponent } from './features/dashboard/home-tab/staff-mismatch-banner/staff-mismatch-banner.component';
import { MigratedUserTermsConditionsComponent } from './features/migrated-user-terms-conditions/migrated-user-terms-conditions.component';
import { SatisfactionSurveyComponent } from './features/satisfaction-survey/satisfaction-survey.component';
import { SentryErrorHandler } from './SentryErrorHandler.component';
import { UsernameFoundComponent } from '@features/forgot-your-username-or-password/username-found/username-found.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -151,6 +151,7 @@ import { UsernameFoundComponent } from '@features/forgot-your-username-or-passwo
SelectStarterJobRolesComponent,
SecurityQuestionAnswerNotMatchComponent,
UserAccountNotFoundComponent,
VacanciesAndTurnoverLoginMessage,
],
imports: [
Angulartics2Module.forRoot({
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/core/model/userDetails.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface UserDetails {
updatedBy?: string;
username?: string;
canManageWdfClaims?: boolean;
lastViewedVacanciesAndTurnoverMessage?: string;
}

export enum UserStatus {
Expand Down
20 changes: 17 additions & 3 deletions frontend/src/app/core/services/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export class UserService {
}

public getLoggedInUser(): Observable<UserDetails> {
return this.http.get<UserDetails>(`${environment.appRunnerEndpoint}/api/user/me`).pipe(tap((user) => (this.loggedInUser = user)));
return this.http
.get<UserDetails>(`${environment.appRunnerEndpoint}/api/user/me`)
.pipe(tap((user) => (this.loggedInUser = user)));
}

public get returnUrl() {
Expand Down Expand Up @@ -137,7 +139,10 @@ export class UserService {
* PUT /api/user/establishment/:establishmentUID/:userUID
*/
public updateUserDetails(workplaceUid: string, userUid: string, userDetails: UserDetails): Observable<UserDetails> {
return this.http.put<UserDetails>(`${environment.appRunnerEndpoint}/api/user/establishment/${workplaceUid}/${userUid}`, userDetails);
return this.http.put<UserDetails>(
`${environment.appRunnerEndpoint}/api/user/establishment/${workplaceUid}/${userUid}`,
userDetails,
);
}

public updateAdminUserDetails(userUid: string, userDetails: UserDetails): Observable<UserDetails> {
Expand Down Expand Up @@ -165,7 +170,9 @@ export class UserService {
*/
public getEstablishments(wdf: boolean = false): Observable<GetWorkplacesResponse> {
const params = wdf ? new HttpParams().set('wdf', `${wdf}`) : null;
return this.http.get<GetWorkplacesResponse>(`${environment.appRunnerEndpoint}/api/user/my/establishments`, { params });
return this.http.get<GetWorkplacesResponse>(`${environment.appRunnerEndpoint}/api/user/my/establishments`, {
params,
});
}

/*
Expand All @@ -176,4 +183,11 @@ export class UserService {
.get<GetAllUsersResponse>(`${environment.appRunnerEndpoint}/api/user/establishment/${workplaceUid}`)
.pipe(map((response) => response.users));
}

public updateLastViewedVacanciesAndTurnoverMessage() {
return this.http.post(
`${environment.appRunnerEndpoint}/api/user/update-last-viewed-vacancies-and-turnover-message/${this.loggedInUser.uid}`,
{},
);
}
}
27 changes: 17 additions & 10 deletions frontend/src/app/core/test-utils/MockAuthService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { WorkplaceDataOwner } from '@core/model/my-workplaces.model';
import { AuthService } from '@core/services/auth.service';
import { EstablishmentService } from '@core/services/establishment.service';
import { PermissionsService } from '@core/services/permissions/permissions.service';
Expand Down Expand Up @@ -81,15 +82,21 @@ export class MockAuthService extends AuthService {
}

public authenticate(username: string, password: string): Observable<any> {
return of({
body: {
role: this._isAdmin ? 'Admin' : 'Edit',
agreedUpdatedTerms: true,
establishment: {
employerTypeSet: this._employerTypeSet,
uid: 'mockuid'
}
},
});
return of(mockAuthenticateResponse(this._isAdmin, this._employerTypeSet));
}
}

export const mockAuthenticateResponse = (isAdmin = false, employerTypeSet = true): any => {
return {
body: {
role: isAdmin ? 'Admin' : 'Edit',
agreedUpdatedTerms: true,
lastViewedVacanciesAndTurnoverMessage: new Date().toISOString(),
establishment: {
employerTypeSet,
uid: 'mockuid',
dataOwner: WorkplaceDataOwner.Workplace,
},
},
};
};
Loading

0 comments on commit 1333c67

Please sign in to comment.