From 2b07f723f824ad0566e67c4999e742de93b73f2f Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 6 Nov 2023 17:51:33 -0800
Subject: [PATCH 01/49] added moment.js as dependency
---
backend/package-lock.json | 9 +++++++++
backend/package.json | 1 +
2 files changed, 10 insertions(+)
diff --git a/backend/package-lock.json b/backend/package-lock.json
index ab1e6afee..f03393b77 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -26,6 +26,7 @@
"jsonwebtoken": "^9.0.1",
"lodash": "^4.17.21",
"memory-cache": "^0.2.0",
+ "moment": "^2.29.4",
"morgan": "^1.10.0",
"nconf": "^0.12.0",
"nocache": "^3.0.4",
@@ -4844,6 +4845,14 @@
"node": "*"
}
},
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/morgan": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz",
diff --git a/backend/package.json b/backend/package.json
index 5b767045f..0a87d28ed 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -21,6 +21,7 @@
"jsonwebtoken": "^9.0.1",
"lodash": "^4.17.21",
"memory-cache": "^0.2.0",
+ "moment": "^2.29.4",
"morgan": "^1.10.0",
"nconf": "^0.12.0",
"nocache": "^3.0.4",
From 805eba8513d5f2306868723f34afb1a8e2929100 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 6 Nov 2023 17:53:28 -0800
Subject: [PATCH 02/49] migration to insert data into 'employee_count_range'
table
---
.../V1.0.2__employee_count_range.sql | 49 +++++++++++++++++++
1 file changed, 49 insertions(+)
create mode 100644 backend/db/migrations/V1.0.2__employee_count_range.sql
diff --git a/backend/db/migrations/V1.0.2__employee_count_range.sql b/backend/db/migrations/V1.0.2__employee_count_range.sql
new file mode 100644
index 000000000..079fbeec6
--- /dev/null
+++ b/backend/db/migrations/V1.0.2__employee_count_range.sql
@@ -0,0 +1,49 @@
+/*
+ Update Summary:
+ - this migration inserts initial data into the 'employee_count_range' table
+*/
+
+insert into pay_transparency.employee_count_range (
+ employee_count_range_id,
+ employee_count_range,
+ create_user,
+ update_user,
+ expiry_date
+)
+values (
+ gen_random_uuid(),
+ '50-299',
+ user,
+ user,
+ TO_DATE('9999-12-31', 'YYYY-MM-DD')
+);
+
+insert into pay_transparency.employee_count_range (
+ employee_count_range_id,
+ employee_count_range,
+ create_user,
+ update_user,
+ expiry_date
+)
+values (
+ gen_random_uuid(),
+ '300-999',
+ user,
+ user,
+ TO_DATE('9999-12-31', 'YYYY-MM-DD')
+);
+
+insert into pay_transparency.employee_count_range (
+ employee_count_range_id,
+ employee_count_range,
+ create_user,
+ update_user,
+ expiry_date
+)
+values (
+ gen_random_uuid(),
+ '1000 or more',
+ user,
+ user,
+ TO_DATE('9999-12-31', 'YYYY-MM-DD')
+);
\ No newline at end of file
From 7e565131105d1754a80f4e5894675e0f0379ee7c Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 6 Nov 2023 17:55:40 -0800
Subject: [PATCH 03/49] added a new route '/codes/employee_count_range'.
created a new 'code-service' to support the implementation of this route.
added a test case for the new service.
---
backend/src/app.ts | 37 +++++++-------
backend/src/v1/routes/code-routes.ts | 13 +++++
backend/src/v1/services/code-service.spec.ts | 52 +++++++++++++++++++
backend/src/v1/services/code-service.ts | 53 ++++++++++++++++++++
4 files changed, 138 insertions(+), 17 deletions(-)
create mode 100644 backend/src/v1/routes/code-routes.ts
create mode 100644 backend/src/v1/services/code-service.spec.ts
create mode 100644 backend/src/v1/services/code-service.ts
diff --git a/backend/src/app.ts b/backend/src/app.ts
index 846c442c5..d695ab9da 100644
--- a/backend/src/app.ts
+++ b/backend/src/app.ts
@@ -1,23 +1,24 @@
import express from "express";
-import morgan from "morgan";
-import helmet from "helmet";
-import cors from "cors";
import bodyParser from "body-parser";
-const app = express();
-const apiRouter = express.Router();
-import {fileUploadRouter} from './v1/routes/file-upload-routes';
+import cors from "cors";
+import session from 'express-session';
+import helmet from "helmet";
+import morgan from "morgan";
import noCache from 'nocache';
-import {config} from './config';
import passport from 'passport';
-import {auth} from './v1/services/auth-service';
-import {utils} from './v1/services/utils-service';
-import session from 'express-session';
-import {resolve} from 'path';
+import { resolve } from 'path';
+import { config } from './config';
import authRouter from './v1/routes/auth-routes';
+import codeRouter from './v1/routes/code-routes';
+import { fileUploadRouter } from './v1/routes/file-upload-routes';
import userRouter from './v1/routes/user-info-routes';
+import { auth } from './v1/services/auth-service';
+import { utils } from './v1/services/utils-service';
+const app = express();
+const apiRouter = express.Router();
-import {logger} from "./logger";
+import { logger } from "./logger";
const JWTStrategy = require('passport-jwt').Strategy;
const ExtractJwt = require('passport-jwt').ExtractJwt;
const OidcStrategy = require('passport-openidconnect-keycloak-idp').Strategy;
@@ -34,7 +35,7 @@ app.use(noCache());
//tells the app to use json as means of transporting data
-app.use(bodyParser.json({limit: "50mb", extended: true}));
+app.use(bodyParser.json({ limit: "50mb", extended: true }));
app.use(bodyParser.urlencoded({
extended: true,
limit: "50mb"
@@ -47,13 +48,13 @@ const cookie = {
//sets cookies for security purposes (prevent cookie access, allow secure connections only, etc)
-const sess= {
+const sess = {
name: 'fin_pay_transparency_cookie',
secret: config.get('oidc:clientSecret'),
resave: false,
saveUninitialized: true,
cookie: cookie,
- store: new fileSession({path: resolve('./', config.get('server:sessionPath'))}),
+ store: new fileSession({ path: resolve('./', config.get('server:sessionPath')) }),
};
if ('production' === config.get('environment')) {
app.set("trust proxy", 1);
@@ -89,7 +90,7 @@ function addLoginPassportUse(discovery, strategyName, callbackURI, kc_idp_hint)
profile.jwt = accessToken;
profile._json = parseJwt(accessToken);
profile.refreshToken = refreshToken;
- profile.idToken= idToken;
+ profile.idToken = idToken;
return done(null, profile);
}));
}
@@ -149,4 +150,6 @@ apiRouter.get("/", (req, res, next) => {
apiRouter.use('/auth', authRouter);
apiRouter.use('/user', userRouter);
apiRouter.use("/v1/file-upload", fileUploadRouter);
-export {app};
+apiRouter.use("/v1/codes", codeRouter);
+export { app };
+
diff --git a/backend/src/v1/routes/code-routes.ts b/backend/src/v1/routes/code-routes.ts
new file mode 100644
index 000000000..39d332370
--- /dev/null
+++ b/backend/src/v1/routes/code-routes.ts
@@ -0,0 +1,13 @@
+import express from 'express';
+import passport from 'passport';
+import { auth } from "../services/auth-service";
+import { codeService } from '../services/code-service';
+
+const isValidBackendToken = auth.isValidBackendToken();
+const router = express.Router();
+
+router.get('/employee_count_range ', passport.authenticate('jwt', { session: false }), isValidBackendToken, async (req, res) => {
+ res.sendStatus(200).json(codeService.getAllEmployeeCountRanges);
+});
+
+export = router;
diff --git a/backend/src/v1/services/code-service.spec.ts b/backend/src/v1/services/code-service.spec.ts
new file mode 100644
index 000000000..547bf4948
--- /dev/null
+++ b/backend/src/v1/services/code-service.spec.ts
@@ -0,0 +1,52 @@
+import prisma from '../prisma/prisma-client';
+import { codeService } from './code-service';
+
+jest.mock('../prisma/prisma-client', () => {
+ return {
+ employee_count_range: {
+ findMany: jest.fn()
+ }
+ }
+})
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+
+describe("getAllEmployeeCountRanges", () => {
+ it("returns an array of code values", async () => {
+ (prisma.employee_count_range.findMany as jest.Mock).mockResolvedValue([
+ {
+ employee_count_range_id: 'ea8b2547-4e93-4bfa-aec1-3e90f91027dd',
+ employee_count_range: '1-99'
+ },
+ {
+ employee_count_range_id: 'c7e1c454-7db9-46c6-b250-1567a543d22f',
+ employee_count_range: '100-499'
+ },
+ {
+ employee_count_range_id: '5f26cc90-7960-4e14-9700-87ecd75f0a0f',
+ employee_count_range: '500+'
+ },
+ ])
+
+ const resp1 = await codeService.getAllEmployeeCountRanges();
+ const values1 = resp1.map((d: any) => d.employee_count_range);
+ expect(resp1.length).toBe(3);
+ expect(values1).toContain("1-99");
+ expect(values1).toContain("100-499");
+ expect(values1).toContain("500+");
+ expect(prisma.employee_count_range.findMany).toBeCalledTimes(1);
+
+ //repeat the call to confirm that only one first call caused a DB
+ //query, and the second call returned cached data
+ const resp2 = await codeService.getAllEmployeeCountRanges();
+ const values2 = resp1.map((d: any) => d.employee_count_range);
+ expect(resp2.length).toBe(3);
+ expect(values2).toContain("1-99");
+ expect(values2).toContain("100-499");
+ expect(values2).toContain("500+");
+ expect(prisma.employee_count_range.findMany).toBeCalledTimes(1);
+ })
+})
diff --git a/backend/src/v1/services/code-service.ts b/backend/src/v1/services/code-service.ts
new file mode 100644
index 000000000..bb1c8db4d
--- /dev/null
+++ b/backend/src/v1/services/code-service.ts
@@ -0,0 +1,53 @@
+import moment from 'moment';
+import prisma from '../prisma/prisma-client';
+
+const cacheExpirationTimeHours = 25;
+
+let employeeCountRangeCache: any[] = [];
+let employeeCountRangeCacheExpiryDate: moment.Moment = null;
+
+const codeService = {
+
+ /* This function returns a list of untyped objects representing
+ employee count ranges.
+ The employee count ranges are stored in the database. Because the values
+ change infrequently, we reduce roundtrips to the database by caching (in memory)
+ the full list of all values. */
+ async getAllEmployeeCountRanges() {
+
+ const now = moment();
+
+ if (employeeCountRangeCacheExpiryDate && moment(now).isAfter(employeeCountRangeCacheExpiryDate)) {
+ //Cache has expired. Clear it
+ employeeCountRangeCache = [];
+ employeeCountRangeCacheExpiryDate = null;
+ }
+
+ //No cached values, so fetch from database
+ if (!employeeCountRangeCache || !employeeCountRangeCache.length) {
+ employeeCountRangeCache = await prisma.employee_count_range.findMany({
+ select: {
+ employee_count_range_id: true,
+ employee_count_range: true
+ },
+ where: {
+ effective_date: {
+ lte: now.toDate(),
+ },
+ expiry_date: {
+ gt: now.toDate(),
+ },
+ }
+ });
+
+ //Set the cache to expire at some time in the future.
+ employeeCountRangeCacheExpiryDate = moment().add(cacheExpirationTimeHours, "hours")
+ }
+
+ return employeeCountRangeCache;
+ }
+
+};
+
+export { codeService };
+
From 9b74fcaa08ca4cf94b8a4956a385559bb286ba78 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Tue, 7 Nov 2023 08:55:42 -0800
Subject: [PATCH 04/49] replaced underscores with dashes in the route
'//codes/employee-count-range' to be consistent with the '/file-upload'
endpoint.
---
backend/src/v1/routes/code-routes.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/src/v1/routes/code-routes.ts b/backend/src/v1/routes/code-routes.ts
index 39d332370..c19217686 100644
--- a/backend/src/v1/routes/code-routes.ts
+++ b/backend/src/v1/routes/code-routes.ts
@@ -6,7 +6,7 @@ import { codeService } from '../services/code-service';
const isValidBackendToken = auth.isValidBackendToken();
const router = express.Router();
-router.get('/employee_count_range ', passport.authenticate('jwt', { session: false }), isValidBackendToken, async (req, res) => {
+router.get('/employee-count-range ', passport.authenticate('jwt', { session: false }), isValidBackendToken, async (req, res) => {
res.sendStatus(200).json(codeService.getAllEmployeeCountRanges);
});
From 08aeb788b6161c62e68571767809c3520d341c65 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Tue, 7 Nov 2023 10:58:59 -0800
Subject: [PATCH 05/49] removed debug statement
---
backend/src/v1/services/auth-service.spec.ts | 19 ++++++++++---------
1 file changed, 10 insertions(+), 9 deletions(-)
diff --git a/backend/src/v1/services/auth-service.spec.ts b/backend/src/v1/services/auth-service.spec.ts
index 83a2e967d..6fe5d2b22 100644
--- a/backend/src/v1/services/auth-service.spec.ts
+++ b/backend/src/v1/services/auth-service.spec.ts
@@ -1,8 +1,8 @@
import axios from 'axios';
import jsonwebtoken from 'jsonwebtoken';
+import { config } from '../../config';
import { auth } from './auth-service';
import { utils } from './utils-service';
-import { config } from '../../config';
//Mock the entire axios module so we never inadvertently make real
//HTTP calls to remote services
@@ -42,7 +42,6 @@ jest.mock('./utils-service', () => {
jest.mock('../../config')
const actualConfig = jest.requireActual('../../config').config
-console.log(actualConfig)
afterEach(() => {
jest.clearAllMocks();
@@ -263,13 +262,15 @@ describe("refreshJWT", () => {
describe("generateUiToken", () => {
it("generates a new JWT token that expires in 30 minute (1800 seconds)", async () => {
- (config.get as jest.Mock).mockImplementation((key) => { return {
- "tokenGenerate:issuer": "issuer",
- "server:frontend": "server-frontend",
- "tokenGenerate:audience": "audience",
- "tokenGenerate:privateKey": actualConfig.get("tokenGenerate:privateKey")
- }[key]})
-
+ (config.get as jest.Mock).mockImplementation((key) => {
+ return {
+ "tokenGenerate:issuer": "issuer",
+ "server:frontend": "server-frontend",
+ "tokenGenerate:audience": "audience",
+ "tokenGenerate:privateKey": actualConfig.get("tokenGenerate:privateKey")
+ }[key]
+ })
+
const token = auth.generateUiToken();
const payload = jsonwebtoken.decode(token);
From 628ac140e771051d99948855ee103407d73518ab Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Tue, 7 Nov 2023 11:43:29 -0800
Subject: [PATCH 06/49] made employee_count_range.expiry_date an optional
column with null as the default value. removed the 'expiry_date' column from
insert statements (so nulls are inserted)
---
.../V1.0.2__employee_count_range.sql | 25 +++++++++----------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/backend/db/migrations/V1.0.2__employee_count_range.sql b/backend/db/migrations/V1.0.2__employee_count_range.sql
index 079fbeec6..6cba35f1b 100644
--- a/backend/db/migrations/V1.0.2__employee_count_range.sql
+++ b/backend/db/migrations/V1.0.2__employee_count_range.sql
@@ -1,49 +1,48 @@
/*
Update Summary:
- - this migration inserts initial data into the 'employee_count_range' table
+ - drops a not null constraint on pay_transparency.employee_count_range.expiry_date and changes
+ the default value to null.
+ - inserts initial data into the 'employee_count_range' table
*/
+alter table pay_transparency.employee_count_range alter column expiry_date drop not null;
+alter table pay_transparency.employee_count_range alter column expiry_date set default null;
+
insert into pay_transparency.employee_count_range (
employee_count_range_id,
employee_count_range,
create_user,
- update_user,
- expiry_date
+ update_user
)
values (
gen_random_uuid(),
'50-299',
user,
- user,
- TO_DATE('9999-12-31', 'YYYY-MM-DD')
+ user
);
insert into pay_transparency.employee_count_range (
employee_count_range_id,
employee_count_range,
create_user,
- update_user,
- expiry_date
+ update_user
)
values (
gen_random_uuid(),
'300-999',
user,
- user,
- TO_DATE('9999-12-31', 'YYYY-MM-DD')
+ user
);
insert into pay_transparency.employee_count_range (
employee_count_range_id,
employee_count_range,
create_user,
- update_user,
- expiry_date
+ update_user
)
values (
gen_random_uuid(),
'1000 or more',
user,
- user,
- TO_DATE('9999-12-31', 'YYYY-MM-DD')
+ user
);
\ No newline at end of file
From 3f716737f51a958c799b3764deaf2b26fa893884 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Tue, 7 Nov 2023 11:47:54 -0800
Subject: [PATCH 07/49] updated column employee_count_range.expiry_date to be
optional with no default
---
backend/src/v1/prisma/schema.prisma | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/backend/src/v1/prisma/schema.prisma b/backend/src/v1/prisma/schema.prisma
index 99dd122bb..803571ee2 100644
--- a/backend/src/v1/prisma/schema.prisma
+++ b/backend/src/v1/prisma/schema.prisma
@@ -26,7 +26,7 @@ model employee_count_range {
create_date DateTime @default(now()) @db.Timestamp(6)
update_date DateTime @default(now()) @db.Timestamp(6)
effective_date DateTime @default(now()) @db.Timestamp(6)
- expiry_date DateTime @default(now()) @db.Timestamp(6)
+ expiry_date DateTime? @db.Timestamp(6)
create_user String @db.VarChar(255)
update_user String @db.VarChar(255)
pay_transparency_report pay_transparency_report[]
From 125ea89a846e0cddc2d3c455526d32db258f7d93 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Tue, 7 Nov 2023 11:48:54 -0800
Subject: [PATCH 08/49] code cleanup to test for getAllEmployeeCountRanges()
---
backend/src/v1/services/code-service.spec.ts | 33 ++++++++++++--------
1 file changed, 20 insertions(+), 13 deletions(-)
diff --git a/backend/src/v1/services/code-service.spec.ts b/backend/src/v1/services/code-service.spec.ts
index 547bf4948..cfac5f250 100644
--- a/backend/src/v1/services/code-service.spec.ts
+++ b/backend/src/v1/services/code-service.spec.ts
@@ -16,7 +16,7 @@ afterEach(() => {
describe("getAllEmployeeCountRanges", () => {
it("returns an array of code values", async () => {
- (prisma.employee_count_range.findMany as jest.Mock).mockResolvedValue([
+ const mockDBResp = [
{
employee_count_range_id: 'ea8b2547-4e93-4bfa-aec1-3e90f91027dd',
employee_count_range: '1-99'
@@ -29,24 +29,31 @@ describe("getAllEmployeeCountRanges", () => {
employee_count_range_id: '5f26cc90-7960-4e14-9700-87ecd75f0a0f',
employee_count_range: '500+'
},
- ])
+ ];
+ (prisma.employee_count_range.findMany as jest.Mock).mockResolvedValue(mockDBResp)
+
+ // Expect the first call to the function to cause the implementation
+ // provide a response by fetching data from a database (also confirm the
+ //response contains the expected data)
const resp1 = await codeService.getAllEmployeeCountRanges();
const values1 = resp1.map((d: any) => d.employee_count_range);
- expect(resp1.length).toBe(3);
- expect(values1).toContain("1-99");
- expect(values1).toContain("100-499");
- expect(values1).toContain("500+");
expect(prisma.employee_count_range.findMany).toBeCalledTimes(1);
-
- //repeat the call to confirm that only one first call caused a DB
- //query, and the second call returned cached data
+ expect(resp1.length).toBe(3);
+ expect(values1).toContain(mockDBResp[0].employee_count_range);
+ expect(values1).toContain(mockDBResp[1].employee_count_range);
+ expect(values1).toContain(mockDBResp[2].employee_count_range);
+
+ // Repeat the call to confirm that only one first call caused a DB
+ // query, and the second call returned cached data (also confirm the
+ //response contains the expected data)
const resp2 = await codeService.getAllEmployeeCountRanges();
const values2 = resp1.map((d: any) => d.employee_count_range);
- expect(resp2.length).toBe(3);
- expect(values2).toContain("1-99");
- expect(values2).toContain("100-499");
- expect(values2).toContain("500+");
expect(prisma.employee_count_range.findMany).toBeCalledTimes(1);
+ expect(resp2.length).toBe(3);
+ expect(values2).toContain(mockDBResp[0].employee_count_range);
+ expect(values2).toContain(mockDBResp[1].employee_count_range);
+ expect(values2).toContain(mockDBResp[2].employee_count_range);
+
})
})
From 908580ef90c7c2a1685e68a40ca5aabf36b95ffb Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Tue, 7 Nov 2023 11:50:44 -0800
Subject: [PATCH 09/49] updated query in getAllEmployeeCountRanges() to include
records with null expiry_date (if effective_date is valid)
---
backend/src/v1/services/code-service.ts | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/backend/src/v1/services/code-service.ts b/backend/src/v1/services/code-service.ts
index bb1c8db4d..7c187e476 100644
--- a/backend/src/v1/services/code-service.ts
+++ b/backend/src/v1/services/code-service.ts
@@ -30,13 +30,23 @@ const codeService = {
employee_count_range_id: true,
employee_count_range: true
},
+ //Only fetch records that are within the effective time range
+ //i.e. current time >= effective date and either no expiry date is given,
+ //or expiry date is in the future
where: {
effective_date: {
lte: now.toDate(),
},
- expiry_date: {
- gt: now.toDate(),
- },
+ OR: [
+ {
+ expiry_date: null
+ },
+ {
+ expiry_date: {
+ gt: now.toDate(),
+ }
+ }
+ ]
}
});
From b144aa07f233d209d1600511fd5c2c0c3c1b3e69 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Wed, 8 Nov 2023 13:41:59 -0800
Subject: [PATCH 10/49] fixed merge conflicts
---
backend/src/v1/routes/code-routes.ts | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/backend/src/v1/routes/code-routes.ts b/backend/src/v1/routes/code-routes.ts
index 98a5eca12..d3d9a3094 100644
--- a/backend/src/v1/routes/code-routes.ts
+++ b/backend/src/v1/routes/code-routes.ts
@@ -1,22 +1,11 @@
import express from 'express';
import passport from 'passport';
import { auth } from "../services/auth-service";
-<<<<<<< HEAD
import { codeService } from '../services/code-service';
-=======
-import { codeService } from "../services/code-service";
->>>>>>> bb0ba5ad6a7c37899f2a4d0e4842636583f0755e
const isValidBackendToken = auth.isValidBackendToken();
const router = express.Router();
-<<<<<<< HEAD
-router.get('/employee-count-range ', passport.authenticate('jwt', { session: false }), isValidBackendToken, async (req, res) => {
- res.sendStatus(200).json(codeService.getAllEmployeeCountRanges);
-});
-
-export = router;
-=======
router.get('/employee-count-ranges', passport.authenticate('jwt', { session: false }), isValidBackendToken, async (req, res) => {
const body = await codeService.getAllEmployeeCountRanges()
res.status(200).json(body);
@@ -24,4 +13,3 @@ router.get('/employee-count-ranges', passport.authenticate('jwt', { session: fal
export = router;
->>>>>>> bb0ba5ad6a7c37899f2a4d0e4842636583f0755e
From 936dadefc7b43a42d782ddc533c644e61743bd1e Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Wed, 8 Nov 2023 13:42:39 -0800
Subject: [PATCH 11/49] fixed merge conflicts
---
backend/src/v1/routes/code-routes.ts | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/backend/src/v1/routes/code-routes.ts b/backend/src/v1/routes/code-routes.ts
index 98a5eca12..7aab400f8 100644
--- a/backend/src/v1/routes/code-routes.ts
+++ b/backend/src/v1/routes/code-routes.ts
@@ -1,22 +1,11 @@
import express from 'express';
import passport from 'passport';
import { auth } from "../services/auth-service";
-<<<<<<< HEAD
-import { codeService } from '../services/code-service';
-=======
import { codeService } from "../services/code-service";
->>>>>>> bb0ba5ad6a7c37899f2a4d0e4842636583f0755e
const isValidBackendToken = auth.isValidBackendToken();
const router = express.Router();
-<<<<<<< HEAD
-router.get('/employee-count-range ', passport.authenticate('jwt', { session: false }), isValidBackendToken, async (req, res) => {
- res.sendStatus(200).json(codeService.getAllEmployeeCountRanges);
-});
-
-export = router;
-=======
router.get('/employee-count-ranges', passport.authenticate('jwt', { session: false }), isValidBackendToken, async (req, res) => {
const body = await codeService.getAllEmployeeCountRanges()
res.status(200).json(body);
@@ -24,4 +13,3 @@ router.get('/employee-count-ranges', passport.authenticate('jwt', { session: fal
export = router;
->>>>>>> bb0ba5ad6a7c37899f2a4d0e4842636583f0755e
From ccd366dd726c45ae2d31b8b572c856bce63e9685 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Thu, 9 Nov 2023 17:26:29 -0800
Subject: [PATCH 12/49] added a new 'store' to manage retrieval and storage of
static content (employee count ranges and naics codes) from the backend.
---
frontend/src/store/modules/codeStore.ts | 52 +++++++++++++++++++++++++
1 file changed, 52 insertions(+)
create mode 100644 frontend/src/store/modules/codeStore.ts
diff --git a/frontend/src/store/modules/codeStore.ts b/frontend/src/store/modules/codeStore.ts
new file mode 100644
index 000000000..de3836281
--- /dev/null
+++ b/frontend/src/store/modules/codeStore.ts
@@ -0,0 +1,52 @@
+import { defineStore, storeToRefs } from 'pinia';
+import { ref, watch } from 'vue';
+import ApiService from '../../common/apiService.js';
+import { authStore } from './auth.js';
+
+/*
+The CodeStore houses static data that is ultimately to be used to populate
+lists of options in the UI. It is responsible for fetching
+those data from the backend and sharing them via a reactive interface.
+*/
+export const useCodeStore = defineStore('code', () => {
+
+ const auth = authStore();
+ const { isAuthenticated } = storeToRefs(auth)
+
+ const employeeCountRanges = ref([]);
+ const naicsCodes = ref([]);
+
+ const setEmployeeCountRanges = (val) => {
+ employeeCountRanges.value = val;
+ }
+ const setNaicsCodes = (val) => {
+ naicsCodes.value = val;
+ }
+
+ const fetchAllCodes = async () => {
+ const employeeCountRanges = await ApiService.getEmployeeCountRanges()
+ setEmployeeCountRanges(employeeCountRanges)
+
+ const naicsCodes = await ApiService.getNaicsCodes()
+ setNaicsCodes(naicsCodes)
+ }
+
+ // Watch for changes to the isAuthenticated property in the AuthStore. When set
+ // to true initiate API calls to fetch static data from the backend. (The API
+ // calls require a token showing the user is authenticated.)
+ watch(
+ isAuthenticated,
+ async (isAuthenticated) => {
+ if (isAuthenticated) {
+ fetchAllCodes();
+ }
+ },
+ { immediate: true }
+ )
+
+ // Return the public interface for the store
+ return {
+ employeeCountRanges,
+ naicsCodes
+ }
+})
\ No newline at end of file
From 4c4c6fb17fda136a69d07e791fb285ada5dbbb18 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Thu, 9 Nov 2023 17:27:20 -0800
Subject: [PATCH 13/49] added new functions getEmployeeCountRanges() and
getNaicsCodes() to communicate with backend endpoints
---
frontend/src/common/apiService.js | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/frontend/src/common/apiService.js b/frontend/src/common/apiService.js
index 05c8ec5f4..40997303c 100644
--- a/frontend/src/common/apiService.js
+++ b/frontend/src/common/apiService.js
@@ -59,6 +59,26 @@ export default {
delete apiAxios.defaults.headers.common['Authorization'];
}
},
+ async getEmployeeCountRanges() {
+ try{
+ const resp = await apiAxios.get(ApiRoutes.EMPLOYEE_COUNT_RANGES);
+ if (resp?.data) {
+ return resp.data;
+ }
+ throw new Error("Unable to fetch employee count ranges from API");
+ } catch(e) {
+ console.log(`Failed to get employee count ranges from API - ${e}`);
+ throw e;
+ }
+ },
+ async getNaicsCodes() {
+ try{
+ return ["1", "2", "3"]
+ } catch(e) {
+ console.log(`Failed to get NAICS from API - ${e}`);
+ throw e;
+ }
+ },
async getUserInfo() {
try{
return await apiAxios.get(ApiRoutes.USER);
From 4c53418de519f714027788cbda57b9b13193617c Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Thu, 9 Nov 2023 17:28:28 -0800
Subject: [PATCH 14/49] added EMPLOYEE_COUNT_RANGES with path to backend
endpoint
---
frontend/src/utils/constant.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/utils/constant.js b/frontend/src/utils/constant.js
index d7507ecbd..bb4383013 100644
--- a/frontend/src/utils/constant.js
+++ b/frontend/src/utils/constant.js
@@ -24,7 +24,7 @@ export const ApiRoutes = Object.freeze({
fileUpload: {
BASE_URL: fileUploadRoot,
},
-
+ EMPLOYEE_COUNT_RANGES: baseRoot + "/v1/codes/employee-count-ranges"
});
export const PAGE_TITLES = Object.freeze({
From 30e8e57ed7f8315b38196e44853045f019d971ed Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Thu, 9 Nov 2023 17:29:35 -0800
Subject: [PATCH 15/49] made InputForm route require auth
---
frontend/src/router.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/router.js b/frontend/src/router.js
index cd5a70c56..1091ea741 100644
--- a/frontend/src/router.js
+++ b/frontend/src/router.js
@@ -43,7 +43,7 @@ const router = createRouter({
component: InputForm,
meta: {
pageTitle: PAGE_TITLES.REPORT,
- requiresAuth: false
+ requiresAuth: true
}
},
]
From 214b53b152680fb64c3ee3b5f7a11d4aab735474 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Thu, 9 Nov 2023 17:30:11 -0800
Subject: [PATCH 16/49] fixed route from 'Generate Pay Transparency Report'
button
---
frontend/src/components/Dashboard.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/components/Dashboard.vue b/frontend/src/components/Dashboard.vue
index aeb9df7ee..a070bf325 100644
--- a/frontend/src/components/Dashboard.vue
+++ b/frontend/src/components/Dashboard.vue
@@ -17,7 +17,7 @@
Then click the button below to access a submission
form, where you'll be asked to enter information about your company and upload your CSV file.
- Generate Pay Transparency Report
+ Generate Pay Transparency Report
From 3fa4227df566c56430574a5eb676a82a12fe56b3 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Thu, 9 Nov 2023 17:31:40 -0800
Subject: [PATCH 17/49] integrated employee count ranges and company info
---
frontend/src/components/InputForm.vue | 49 ++++++++++++++++++---------
1 file changed, 33 insertions(+), 16 deletions(-)
diff --git a/frontend/src/components/InputForm.vue b/frontend/src/components/InputForm.vue
index 3fb5e2d25..cb9799bc4 100644
--- a/frontend/src/components/InputForm.vue
+++ b/frontend/src/components/InputForm.vue
@@ -4,7 +4,6 @@
{{ alertMessage }}
-
@@ -15,8 +14,8 @@
-
-
+
+
@@ -55,29 +54,28 @@
-
+
Your Company Information
-
+
+ required disabled>
-
+
+ :items="employeeCountRanges" item-title="employee_count_range" required>
@@ -118,7 +116,7 @@
-
+
@@ -138,6 +136,9 @@ import VueDatePicker from '@vuepic/vue-datepicker';
import '@vuepic/vue-datepicker/dist/main.css'
import PrimaryButton from './util/PrimaryButton.vue';
import ApiService from '../common/apiService';
+import { useCodeStore } from '../store/modules/codeStore';
+import { authStore } from '../store/modules/auth';
+import { mapState, } from 'pinia';
import moment from 'moment';
export default {
@@ -153,6 +154,7 @@ export default {
naicsCode: null,
naicsCodeList: ["2342"],
employeeCount: null,
+ employeeCountOptions: [],
isProcessing: false,
uploadFileValue: null,
minStartDate: moment().subtract(2, "years").format("yyyy-MM"),
@@ -223,8 +225,8 @@ export default {
},
watch: {
startDate(newVal) {
- /* When the startDate changes, automatically adjust the endDate to be
- 12 months later */
+ // When the startDate changes, automatically adjust the endDate to be
+ // 12 months later
if (newVal) {
const startDate = moment(newVal)
const endDate = moment(newVal).add(1, 'years').subtract(1, 'months')
@@ -232,16 +234,31 @@ export default {
}
},
endDate(newVal) {
- /* When the endDate changes, automatically adjust the startDate to be
- 12 months earlier */
+ // When the endDate changes, automatically adjust the startDate to be
+ // 12 months earlier
if (newVal) {
const endDate = moment(newVal)
const startDate = moment(newVal).subtract(1, 'years').add(1, 'months')
this.startDate = startDate.format("yyyy-MM");
}
- }
+ },
+ userInfo: {
+ // Watch for changes to userInfo (from the authStore). Copy company name
+ // and address from that object into state variables in this component.
+ immediate: true,
+ handler(userInfo) {
+ this.companyName = userInfo?.legalName;
+ const address = `${userInfo?.addressLine1 ? userInfo?.addressLine1 : ""} ${userInfo?.addressLine2 ? userInfo?.addressLine2 : ""}`.trim();
+ this.companyAddress = address;
+ }
+ },
},
computed: {
+ ...mapState(useCodeStore, [
+ 'employeeCountRanges',
+ 'naicsCodes'
+ ]),
+ ...mapState(authStore, ['userInfo']),
dataReady() {
return this.validForm && this.uploadFileValue;
},
From 4f2a17d5a4623899f589dc083f92d561bf777e3b Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 10:29:06 -0800
Subject: [PATCH 18/49] updated the pinia/testing library to help with mocking
stores in component tests. upgraded the pinia and vue versions (required to
use pinia/testing)
---
frontend/package.json | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frontend/package.json b/frontend/package.json
index f3bc956a7..01eb0e0be 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -30,7 +30,7 @@
"moment": "^2.29.4",
"nconf": "^0.12.0",
"path": "^0.12.7",
- "pinia": "^2.0.32",
+ "pinia": "^2.1.7",
"prom-client": "^14.1.1",
"purgecss-webpack-plugin": "^5.0.0",
"regenerator-runtime": "^0.13.11",
@@ -40,7 +40,7 @@
"testcafe": "^2.2.0",
"v-viewer": "^3.0.11",
"vite": "^4.1.4",
- "vue": "^3.2.47",
+ "vue": "^3.3.8",
"vue-chartjs": "^5.2.0",
"vue-clipboard2": "^0.3.3",
"vue-meta": "^3.0.0-alpha.10",
@@ -50,6 +50,7 @@
},
"devDependencies": {
"@babel/eslint-parser": "^7.19.1",
+ "@pinia/testing": "^0.1.3",
"@vitejs/plugin-vue": "^4.0.0",
"@vitest/coverage-v8": "^0.33.0",
"@vitest/ui": "^0.33.0",
From a6bf2d1c9ee8828bf99a7c26bde99e3ec7fb3482 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 10:29:17 -0800
Subject: [PATCH 19/49] updated the pinia/testing library to help with mocking
stores in component tests. upgraded the pinia and vue versions (required to
use pinia/testing)
---
frontend/package-lock.json | 617 ++++++++++++++++++-------------------
1 file changed, 293 insertions(+), 324 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a0e609194..34b0a4b3b 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -27,7 +27,7 @@
"moment": "^2.29.4",
"nconf": "^0.12.0",
"path": "^0.12.7",
- "pinia": "^2.0.32",
+ "pinia": "^2.1.7",
"prom-client": "^14.1.1",
"purgecss-webpack-plugin": "^5.0.0",
"regenerator-runtime": "^0.13.11",
@@ -37,7 +37,7 @@
"testcafe": "^2.2.0",
"v-viewer": "^3.0.11",
"vite": "^4.1.4",
- "vue": "^3.2.47",
+ "vue": "^3.3.8",
"vue-chartjs": "^5.2.0",
"vue-clipboard2": "^0.3.3",
"vue-meta": "^3.0.0-alpha.10",
@@ -47,6 +47,7 @@
},
"devDependencies": {
"@babel/eslint-parser": "^7.19.1",
+ "@pinia/testing": "^0.1.3",
"@vitejs/plugin-vue": "^4.0.0",
"@vitest/coverage-v8": "^0.33.0",
"@vitest/ui": "^0.33.0",
@@ -605,9 +606,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.21.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz",
- "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==",
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
+ "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -2938,6 +2939,47 @@
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"dev": true
},
+ "node_modules/@pinia/testing": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.3.tgz",
+ "integrity": "sha512-D2Ds2s69kKFaRf2KCcP1NhNZEg5+we59aRyQalwRm7ygWfLM25nDH66267U3hNvRUOTx8ofL24GzodZkOmB5xw==",
+ "dev": true,
+ "dependencies": {
+ "vue-demi": ">=0.14.5"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "pinia": ">=2.1.5"
+ }
+ },
+ "node_modules/@pinia/testing/node_modules/vue-demi": {
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
@@ -3182,24 +3224,6 @@
"vitest": ">=0.32.0 <1"
}
},
- "node_modules/@vitest/coverage-v8/node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
- },
- "node_modules/@vitest/coverage-v8/node_modules/magic-string": {
- "version": "0.30.2",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
- "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
- "dev": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@vitest/expect": {
"version": "0.33.0",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-0.33.0.tgz",
@@ -3299,24 +3323,6 @@
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/snapshot/node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
- },
- "node_modules/@vitest/snapshot/node_modules/magic-string": {
- "version": "0.30.2",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
- "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
- "dev": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/@vitest/spy": {
"version": "0.33.0",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-0.33.0.tgz",
@@ -3365,49 +3371,49 @@
}
},
"node_modules/@vue/compiler-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
- "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz",
+ "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==",
"dependencies": {
- "@babel/parser": "^7.16.4",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "source-map": "^0.6.1"
+ "source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz",
- "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz",
+ "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==",
"dependencies": {
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/compiler-sfc": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz",
- "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==",
- "dependencies": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-ssr": "3.2.47",
- "@vue/reactivity-transform": "3.2.47",
- "@vue/shared": "3.2.47",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz",
+ "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==",
+ "dependencies": {
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/reactivity-transform": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7",
- "postcss": "^8.1.10",
- "source-map": "^0.6.1"
+ "magic-string": "^0.30.5",
+ "postcss": "^8.4.31",
+ "source-map-js": "^1.0.2"
}
},
"node_modules/@vue/compiler-ssr": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz",
- "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz",
+ "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==",
"dependencies": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/devtools-api": {
@@ -3416,60 +3422,60 @@
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
},
"node_modules/@vue/reactivity": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz",
- "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz",
+ "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==",
"dependencies": {
- "@vue/shared": "3.2.47"
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/reactivity-transform": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz",
- "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz",
+ "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==",
"dependencies": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7"
+ "magic-string": "^0.30.5"
}
},
"node_modules/@vue/runtime-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz",
- "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz",
+ "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==",
"dependencies": {
- "@vue/reactivity": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/reactivity": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"node_modules/@vue/runtime-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz",
- "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz",
+ "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==",
"dependencies": {
- "@vue/runtime-core": "3.2.47",
- "@vue/shared": "3.2.47",
- "csstype": "^2.6.8"
+ "@vue/runtime-core": "3.3.8",
+ "@vue/shared": "3.3.8",
+ "csstype": "^3.1.2"
}
},
"node_modules/@vue/server-renderer": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz",
- "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz",
+ "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==",
"dependencies": {
- "@vue/compiler-ssr": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/shared": "3.3.8"
},
"peerDependencies": {
- "vue": "3.2.47"
+ "vue": "3.3.8"
}
},
"node_modules/@vue/shared": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
- "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
+ "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw=="
},
"node_modules/@vue/test-utils": {
"version": "2.4.1",
@@ -4778,9 +4784,9 @@
}
},
"node_modules/csstype": {
- "version": "2.6.21",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
- "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"node_modules/cuint": {
"version": "0.2.2",
@@ -7782,13 +7788,21 @@
}
},
"node_modules/magic-string": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
- "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "version": "0.30.5",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"dependencies": {
- "sourcemap-codec": "^1.4.8"
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
}
},
+ "node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ },
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -7985,9 +7999,15 @@
}
},
"node_modules/nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
@@ -8392,12 +8412,12 @@
}
},
"node_modules/pinia": {
- "version": "2.0.32",
- "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.32.tgz",
- "integrity": "sha512-8Tw4OrpCSJ028UUyp0gYPP/wyjigLoEceuO/x1G+FlHVf73337e5vLm4uDmrRIoBG1hvaed/eSHnrCFjOc4nkA==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
+ "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
"dependencies": {
"@vue/devtools-api": "^6.5.0",
- "vue-demi": "*"
+ "vue-demi": ">=0.14.5"
},
"funding": {
"url": "https://github.com/sponsors/posva"
@@ -8405,7 +8425,7 @@
"peerDependencies": {
"@vue/composition-api": "^1.4.0",
"typescript": ">=4.4.4",
- "vue": "^2.6.14 || ^3.2.0"
+ "vue": "^2.6.14 || ^3.3.0"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
@@ -8417,9 +8437,9 @@
}
},
"node_modules/pinia/node_modules/vue-demi": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
- "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"hasInstallScript": true,
"bin": {
"vue-demi-fix": "bin/vue-demi-fix.js",
@@ -8620,9 +8640,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"funding": [
{
"type": "opencollective",
@@ -8631,10 +8651,14 @@
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
@@ -9465,12 +9489,6 @@
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
"deprecated": "See https://github.com/lydell/source-map-url#deprecated"
},
- "node_modules/sourcemap-codec": {
- "version": "1.4.8",
- "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
- "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
- "deprecated": "Please use @jridgewell/sourcemap-codec instead"
- },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -11019,12 +11037,6 @@
}
}
},
- "node_modules/vitest/node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
- },
"node_modules/vitest/node_modules/chai": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
@@ -11055,28 +11067,24 @@
"node": ">=6"
}
},
- "node_modules/vitest/node_modules/magic-string": {
- "version": "0.30.2",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
- "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
- "dev": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- },
- "engines": {
- "node": ">=12"
- }
- },
"node_modules/vue": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz",
- "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz",
+ "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==",
"dependencies": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-sfc": "3.2.47",
- "@vue/runtime-dom": "3.2.47",
- "@vue/server-renderer": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-sfc": "3.3.8",
+ "@vue/runtime-dom": "3.3.8",
+ "@vue/server-renderer": "3.3.8",
+ "@vue/shared": "3.3.8"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
}
},
"node_modules/vue-chartjs": {
@@ -11153,9 +11161,9 @@
}
},
"node_modules/vue-quick-chat/node_modules/@vue/compiler-sfc": {
- "version": "2.7.14",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
- "integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
+ "version": "2.7.15",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.15.tgz",
+ "integrity": "sha512-FCvIEevPmgCgqFBH7wD+3B97y7u7oj/Wr69zADBf403Tui377bThTjBvekaZvlRr4IwUAu3M6hYZeULZFJbdYg==",
"dependencies": {
"@babel/parser": "^7.18.4",
"postcss": "^8.4.14",
@@ -11169,11 +11177,6 @@
"deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.",
"hasInstallScript": true
},
- "node_modules/vue-quick-chat/node_modules/csstype": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
- "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
- },
"node_modules/vue-quick-chat/node_modules/luxon": {
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
@@ -11183,11 +11186,11 @@
}
},
"node_modules/vue-quick-chat/node_modules/vue": {
- "version": "2.7.14",
- "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
- "integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
+ "version": "2.7.15",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.15.tgz",
+ "integrity": "sha512-a29fsXd2G0KMRqIFTpRgpSbWaNBK3lpCTOLuGLEDnlHWdjB8fwl6zyYZ8xCrqkJdatwZb4mGHiEfJjnw0Q6AwQ==",
"dependencies": {
- "@vue/compiler-sfc": "2.7.14",
+ "@vue/compiler-sfc": "2.7.15",
"csstype": "^3.1.0"
}
},
@@ -12093,9 +12096,9 @@
}
},
"@babel/parser": {
- "version": "7.21.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz",
- "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ=="
+ "version": "7.23.3",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz",
+ "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw=="
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.18.6",
@@ -13622,6 +13625,24 @@
"integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
"dev": true
},
+ "@pinia/testing": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@pinia/testing/-/testing-0.1.3.tgz",
+ "integrity": "sha512-D2Ds2s69kKFaRf2KCcP1NhNZEg5+we59aRyQalwRm7ygWfLM25nDH66267U3hNvRUOTx8ofL24GzodZkOmB5xw==",
+ "dev": true,
+ "requires": {
+ "vue-demi": ">=0.14.5"
+ },
+ "dependencies": {
+ "vue-demi": {
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
+ "dev": true,
+ "requires": {}
+ }
+ }
+ },
"@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
@@ -13849,23 +13870,6 @@
"std-env": "^3.3.3",
"test-exclude": "^6.0.0",
"v8-to-istanbul": "^9.1.0"
- },
- "dependencies": {
- "@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
- },
- "magic-string": {
- "version": "0.30.2",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
- "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
- "dev": true,
- "requires": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- }
- }
}
},
"@vitest/expect": {
@@ -13942,23 +13946,6 @@
"magic-string": "^0.30.1",
"pathe": "^1.1.1",
"pretty-format": "^29.5.0"
- },
- "dependencies": {
- "@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
- },
- "magic-string": {
- "version": "0.30.2",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
- "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
- "dev": true,
- "requires": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- }
- }
}
},
"@vitest/spy": {
@@ -13997,49 +13984,49 @@
}
},
"@vue/compiler-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.47.tgz",
- "integrity": "sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.8.tgz",
+ "integrity": "sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==",
"requires": {
- "@babel/parser": "^7.16.4",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "source-map": "^0.6.1"
+ "source-map-js": "^1.0.2"
}
},
"@vue/compiler-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.47.tgz",
- "integrity": "sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.8.tgz",
+ "integrity": "sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==",
"requires": {
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/compiler-sfc": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.2.47.tgz",
- "integrity": "sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==",
- "requires": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-ssr": "3.2.47",
- "@vue/reactivity-transform": "3.2.47",
- "@vue/shared": "3.2.47",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.8.tgz",
+ "integrity": "sha512-WMzbUrlTjfYF8joyT84HfwwXo+8WPALuPxhy+BZ6R4Aafls+jDBnSz8PDz60uFhuqFbl3HxRfxvDzrUf3THwpA==",
+ "requires": {
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/reactivity-transform": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7",
- "postcss": "^8.1.10",
- "source-map": "^0.6.1"
+ "magic-string": "^0.30.5",
+ "postcss": "^8.4.31",
+ "source-map-js": "^1.0.2"
}
},
"@vue/compiler-ssr": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.2.47.tgz",
- "integrity": "sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.8.tgz",
+ "integrity": "sha512-hXCqQL/15kMVDBuoBYpUnSYT8doDNwsjvm3jTefnXr+ytn294ySnT8NlsFHmTgKNjwpuFy7XVV8yTeLtNl/P6w==",
"requires": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/devtools-api": {
@@ -14048,57 +14035,57 @@
"integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q=="
},
"@vue/reactivity": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.47.tgz",
- "integrity": "sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.8.tgz",
+ "integrity": "sha512-ctLWitmFBu6mtddPyOKpHg8+5ahouoTCRtmAHZAXmolDtuZXfjL2T3OJ6DL6ezBPQB1SmMnpzjiWjCiMYmpIuw==",
"requires": {
- "@vue/shared": "3.2.47"
+ "@vue/shared": "3.3.8"
}
},
"@vue/reactivity-transform": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.2.47.tgz",
- "integrity": "sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.8.tgz",
+ "integrity": "sha512-49CvBzmZNtcHua0XJ7GdGifM8GOXoUMOX4dD40Y5DxI3R8OUhMlvf2nvgUAcPxaXiV5MQQ1Nwy09ADpnLQUqRw==",
"requires": {
- "@babel/parser": "^7.16.4",
- "@vue/compiler-core": "3.2.47",
- "@vue/shared": "3.2.47",
+ "@babel/parser": "^7.23.0",
+ "@vue/compiler-core": "3.3.8",
+ "@vue/shared": "3.3.8",
"estree-walker": "^2.0.2",
- "magic-string": "^0.25.7"
+ "magic-string": "^0.30.5"
}
},
"@vue/runtime-core": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.47.tgz",
- "integrity": "sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.8.tgz",
+ "integrity": "sha512-qurzOlb6q26KWQ/8IShHkMDOuJkQnQcTIp1sdP4I9MbCf9FJeGVRXJFr2mF+6bXh/3Zjr9TDgURXrsCr9bfjUw==",
"requires": {
- "@vue/reactivity": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/reactivity": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/runtime-dom": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.47.tgz",
- "integrity": "sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.8.tgz",
+ "integrity": "sha512-Noy5yM5UIf9UeFoowBVgghyGGPIDPy1Qlqt0yVsUdAVbqI8eeMSsTqBtauaEoT2UFXUk5S64aWVNJN4MJ2vRdA==",
"requires": {
- "@vue/runtime-core": "3.2.47",
- "@vue/shared": "3.2.47",
- "csstype": "^2.6.8"
+ "@vue/runtime-core": "3.3.8",
+ "@vue/shared": "3.3.8",
+ "csstype": "^3.1.2"
}
},
"@vue/server-renderer": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.2.47.tgz",
- "integrity": "sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.8.tgz",
+ "integrity": "sha512-zVCUw7RFskvPuNlPn/8xISbrf0zTWsTSdYTsUTN1ERGGZGVnRxM2QZ3x1OR32+vwkkCm0IW6HmJ49IsPm7ilLg==",
"requires": {
- "@vue/compiler-ssr": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-ssr": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"@vue/shared": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
- "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.8.tgz",
+ "integrity": "sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw=="
},
"@vue/test-utils": {
"version": "2.4.1",
@@ -15116,9 +15103,9 @@
}
},
"csstype": {
- "version": "2.6.21",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz",
- "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w=="
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
+ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"cuint": {
"version": "0.2.2",
@@ -17372,11 +17359,18 @@
"integrity": "sha512-/M/R0gCDgM+Cv1IuBG1XGdfTFnMEG6PZeT+KGWHO/OG+imqmaD9CH5vHBTycEM3+Kc4uG2Il+tFAuUWLqQOeUA=="
},
"magic-string": {
- "version": "0.25.9",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
- "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==",
+ "version": "0.30.5",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"requires": {
- "sourcemap-codec": "^1.4.8"
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ }
}
},
"make-dir": {
@@ -17523,9 +17517,9 @@
"integrity": "sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ=="
},
"nanoid": {
- "version": "3.3.4",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
- "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw=="
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g=="
},
"natural-compare": {
"version": "1.4.0",
@@ -17816,18 +17810,18 @@
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="
},
"pinia": {
- "version": "2.0.32",
- "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.32.tgz",
- "integrity": "sha512-8Tw4OrpCSJ028UUyp0gYPP/wyjigLoEceuO/x1G+FlHVf73337e5vLm4uDmrRIoBG1hvaed/eSHnrCFjOc4nkA==",
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
+ "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
"requires": {
"@vue/devtools-api": "^6.5.0",
- "vue-demi": "*"
+ "vue-demi": ">=0.14.5"
},
"dependencies": {
"vue-demi": {
- "version": "0.13.11",
- "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz",
- "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==",
+ "version": "0.14.6",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
+ "integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"requires": {}
}
}
@@ -17964,11 +17958,11 @@
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="
},
"postcss": {
- "version": "8.4.21",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz",
- "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==",
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
"requires": {
- "nanoid": "^3.3.4",
+ "nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
}
@@ -18579,11 +18573,6 @@
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw=="
},
- "sourcemap-codec": {
- "version": "1.4.8",
- "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
- "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA=="
- },
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -19739,12 +19728,6 @@
"why-is-node-running": "^2.2.2"
},
"dependencies": {
- "@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
- },
"chai": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
@@ -19768,28 +19751,19 @@
"requires": {
"type-detect": "^4.0.0"
}
- },
- "magic-string": {
- "version": "0.30.2",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.2.tgz",
- "integrity": "sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==",
- "dev": true,
- "requires": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- }
}
}
},
"vue": {
- "version": "3.2.47",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.47.tgz",
- "integrity": "sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==",
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.8.tgz",
+ "integrity": "sha512-5VSX/3DabBikOXMsxzlW8JyfeLKlG9mzqnWgLQLty88vdZL7ZJgrdgBOmrArwxiLtmS+lNNpPcBYqrhE6TQW5w==",
"requires": {
- "@vue/compiler-dom": "3.2.47",
- "@vue/compiler-sfc": "3.2.47",
- "@vue/runtime-dom": "3.2.47",
- "@vue/server-renderer": "3.2.47",
- "@vue/shared": "3.2.47"
+ "@vue/compiler-dom": "3.3.8",
+ "@vue/compiler-sfc": "3.3.8",
+ "@vue/runtime-dom": "3.3.8",
+ "@vue/server-renderer": "3.3.8",
+ "@vue/shared": "3.3.8"
}
},
"vue-chartjs": {
@@ -19852,9 +19826,9 @@
},
"dependencies": {
"@vue/compiler-sfc": {
- "version": "2.7.14",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz",
- "integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==",
+ "version": "2.7.15",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.15.tgz",
+ "integrity": "sha512-FCvIEevPmgCgqFBH7wD+3B97y7u7oj/Wr69zADBf403Tui377bThTjBvekaZvlRr4IwUAu3M6hYZeULZFJbdYg==",
"requires": {
"@babel/parser": "^7.18.4",
"postcss": "^8.4.14",
@@ -19866,22 +19840,17 @@
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz",
"integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ=="
},
- "csstype": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
- "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
- },
"luxon": {
"version": "1.28.1",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.1.tgz",
"integrity": "sha512-gYHAa180mKrNIUJCbwpmD0aTu9kV0dREDrwNnuyFAsO1Wt0EVYSZelPnJlbj9HplzXX/YWXHFTL45kvZ53M0pw=="
},
"vue": {
- "version": "2.7.14",
- "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz",
- "integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==",
+ "version": "2.7.15",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.15.tgz",
+ "integrity": "sha512-a29fsXd2G0KMRqIFTpRgpSbWaNBK3lpCTOLuGLEDnlHWdjB8fwl6zyYZ8xCrqkJdatwZb4mGHiEfJjnw0Q6AwQ==",
"requires": {
- "@vue/compiler-sfc": "2.7.14",
+ "@vue/compiler-sfc": "2.7.15",
"csstype": "^3.1.0"
}
},
From 1d02d82d3ecdd212cb028c076a96f09cca50f604 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 10:30:33 -0800
Subject: [PATCH 20/49] added 'ref' attributes to some form fields so Vuetify
components can be accessed from unit tests. also removed an unneeded state
varaible
---
frontend/src/components/InputForm.vue | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/frontend/src/components/InputForm.vue b/frontend/src/components/InputForm.vue
index cb9799bc4..3eddd2a04 100644
--- a/frontend/src/components/InputForm.vue
+++ b/frontend/src/components/InputForm.vue
@@ -70,12 +70,14 @@
-
+
-
+
@@ -153,8 +155,7 @@ export default {
companyAddress: '',
naicsCode: null,
naicsCodeList: ["2342"],
- employeeCount: null,
- employeeCountOptions: [],
+ employeeCountRange: null,
isProcessing: false,
uploadFileValue: null,
minStartDate: moment().subtract(2, "years").format("yyyy-MM"),
From 45e9a87bafa737abe4c9669139824de53266024e Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 10:32:27 -0800
Subject: [PATCH 21/49] injected a 'test pinia' into the mounted component to
allow testing of behaviour that depends on the 'code store'. added new unit
tests for the 'employee count ranges' and 'naics code
---
.../components/__tests__/InputForm.spec.ts | 75 ++++++++++++++-----
1 file changed, 57 insertions(+), 18 deletions(-)
diff --git a/frontend/src/components/__tests__/InputForm.spec.ts b/frontend/src/components/__tests__/InputForm.spec.ts
index 5945b90ba..b090bb569 100644
--- a/frontend/src/components/__tests__/InputForm.spec.ts
+++ b/frontend/src/components/__tests__/InputForm.spec.ts
@@ -1,27 +1,37 @@
-import { describe, it, expect, beforeEach, afterEach } from 'vitest'
-import { mount } from '@vue/test-utils'
-import { createVuetify } from "vuetify";
-import * as components from 'vuetify/components'
-import * as directives from 'vuetify/directives'
-import InputForm from '../InputForm.vue'
+import { mount } from '@vue/test-utils';
import moment from 'moment';
+import { createTestingPinia } from '@pinia/testing';
+import { afterEach, beforeEach, describe, expect, it } from 'vitest';
+import { createVuetify } from "vuetify";
+import * as components from 'vuetify/components';
+import * as directives from 'vuetify/directives';
+import { useCodeStore } from '../../store/modules/codeStore';
+import InputForm from '../InputForm.vue';
+
describe("InputForm", () => {
- let wrapper = null;
+ let wrapper;
+ let pinia;
beforeEach(() => {
- //create an instance of vuetify so we can inject it into
- //the mounted component, allowing it to behave as it would
+ //create an instances of vuetify and pinia so we can inject them
+ //into the mounted component, allowing it to behave as it would
//in a browser
const vuetify = createVuetify({
components,
directives,
})
+ pinia = createTestingPinia({
+ initialState: {
+ code: {},
+ },
+ });
+
wrapper = mount(InputForm, {
global: {
- plugins: [vuetify],
+ plugins: [vuetify, pinia],
}
})
})
@@ -31,29 +41,58 @@ describe("InputForm", () => {
wrapper.unmount();
}
})
-
- it('Renders with the expected form controls', () => {
+
+ it('Renders with the expected form controls', () => {
expect(wrapper.findAll("#companyName").length).toBe(1);
expect(wrapper.findAll("#companyAddress").length).toBe(1);
expect(wrapper.findAll("#naicsCode").length).toBe(1);
- expect(wrapper.findAll("#employeeCount").length).toBe(1);
+ expect(wrapper.findAll("#employeeCountRange").length).toBe(1);
expect(wrapper.findAll("#startDate").length).toBe(1);
expect(wrapper.findAll("#endDate").length).toBe(1);
expect(wrapper.findAll("#comments").length).toBe(1);
expect(wrapper.find("#csvFile").attributes("type")).toBe("file");
expect(wrapper.find("#csvFile").attributes("accept")).toBe(".csv");
-
+
})
- it('Setting start date causes end date to default to one year later', async () => {
+ it("Form control for 'employee count ranges' is populated with the expected options", async () => {
+
+ // Mock the employeeCountRanges property of the codeStore. InputForm.vue reads
+ // this property and uses it to populate the list of options for the Employee Count Range
+ // form field.
+ const codeStore = useCodeStore(pinia)
+ const employeeCountRanges = [{ "employee_count_range_id": 1, "employee_count_range": "10+" }];
+ await codeStore.$patch({ employeeCountRanges: employeeCountRanges } as any)
- const startDateComponent = wrapper.findComponent({ref: 'startDate'})
- const endDateComponent = wrapper.findComponent({ref: 'endDate'})
+ const employeeCountRangeComponent = wrapper.findComponent({ ref: 'employeeCountRange' })
+ expect(employeeCountRangeComponent.vm.items[0].employee_count_range).toBe(employeeCountRanges[0].employee_count_range);
+ expect(employeeCountRangeComponent.vm.items.length).toBe(employeeCountRanges.length);
+
+ })
+
+ it("Form control for 'NAICS Code' is populated with the expected options", async () => {
+
+ // Mock the naicsCodes property of the codeStore. InputForm.vue reads
+ // this property and uses it to populate the list of options for the NAICS Code
+ // form field.
+ const codeStore = useCodeStore(pinia)
+ const naicsCodes = ["1", "2"];
+ await codeStore.$patch({ naicsCodes: naicsCodes } as any)
+
+ const employeeCountRangeComponent = wrapper.findComponent({ ref: 'naicsCode' })
+ expect(employeeCountRangeComponent.vm.items.length).toBe(naicsCodes.length);
+
+ })
+
+ it('Setting start date causes end date to default to one year later', async () => {
+
+ const startDateComponent = wrapper.findComponent({ ref: 'startDate' })
+ const endDateComponent = wrapper.findComponent({ ref: 'endDate' })
const startDate = moment().subtract(1, "years").format("yyyy-MM");
const expectedEndDate = moment().subtract(1, "months").format("yyyy-MM");
await startDateComponent.setValue(startDate);
-
+
expect(wrapper.vm.$data.startDate).toBe(startDate)
expect(wrapper.vm.$data.endDate).toBe(expectedEndDate)
From 96fbcd2f3c6bcb36e9f5bf2a6739a0c12436bcf7 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 17:11:07 -0800
Subject: [PATCH 22/49] bump vuetify version to 3.4.0
---
frontend/package-lock.json | 20 ++++++++++++--------
frontend/package.json | 2 +-
2 files changed, 13 insertions(+), 9 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 34b0a4b3b..e9dc50dc8 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -43,7 +43,7 @@
"vue-meta": "^3.0.0-alpha.10",
"vue-quick-chat": "^1.2.8",
"vue-router": "^4.1.6",
- "vuetify": "3.2.3"
+ "vuetify": "^3.4.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.19.1",
@@ -11227,9 +11227,9 @@
}
},
"node_modules/vuetify": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.2.3.tgz",
- "integrity": "sha512-o7IJm/P5Ttp9ItF1ytQihsLzv4jxIYVfI4Ypkkqc4A7N2MeTmkDOPGbDNUgJ+G1p2upL00LCbc73A9YM8xYVpg==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.4.0.tgz",
+ "integrity": "sha512-aW3bJGCUN3fhl62yvsb+Hv6TtMWDqiadN0PTbEB8jd9z46/X1ddzQ/fhMjkqBX69sMFtZvENl3YFGU5c88/8qw==",
"engines": {
"node": "^12.20 || >=14.13"
},
@@ -11238,12 +11238,16 @@
"url": "https://github.com/sponsors/johnleider"
},
"peerDependencies": {
+ "typescript": ">=4.7",
"vite-plugin-vuetify": "^1.0.0-alpha.12",
- "vue": "^3.2.0",
+ "vue": "^3.3.0",
"vue-i18n": "^9.0.0",
"webpack-plugin-vuetify": "^2.0.0-alpha.11"
},
"peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ },
"vite-plugin-vuetify": {
"optional": true
},
@@ -19881,9 +19885,9 @@
}
},
"vuetify": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.2.3.tgz",
- "integrity": "sha512-o7IJm/P5Ttp9ItF1ytQihsLzv4jxIYVfI4Ypkkqc4A7N2MeTmkDOPGbDNUgJ+G1p2upL00LCbc73A9YM8xYVpg==",
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.4.0.tgz",
+ "integrity": "sha512-aW3bJGCUN3fhl62yvsb+Hv6TtMWDqiadN0PTbEB8jd9z46/X1ddzQ/fhMjkqBX69sMFtZvENl3YFGU5c88/8qw==",
"requires": {}
},
"w3c-xmlserializer": {
diff --git a/frontend/package.json b/frontend/package.json
index 01eb0e0be..268b62608 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -46,7 +46,7 @@
"vue-meta": "^3.0.0-alpha.10",
"vue-quick-chat": "^1.2.8",
"vue-router": "^4.1.6",
- "vuetify": "3.2.3"
+ "vuetify": "^3.4.0"
},
"devDependencies": {
"@babel/eslint-parser": "^7.19.1",
From 50d0e6518d4166c1e0440f94b0362825d31a230e Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 17:11:52 -0800
Subject: [PATCH 23/49] add new function to post the file upload submission
---
frontend/src/common/apiService.js | 12 ++++++++++++
frontend/src/utils/constant.js | 3 ++-
2 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/frontend/src/common/apiService.js b/frontend/src/common/apiService.js
index 40997303c..0cc8e35a2 100644
--- a/frontend/src/common/apiService.js
+++ b/frontend/src/common/apiService.js
@@ -59,6 +59,18 @@ export default {
delete apiAxios.defaults.headers.common['Authorization'];
}
},
+ async postSubmission(formData) {
+ try{
+ const resp = await apiAxios.post(ApiRoutes.POST_SUBMISSION, formData);
+ if (resp?.data) {
+ return resp.data;
+ }
+ throw new Error("Unable to post the submission");
+ } catch(e) {
+ console.log(`Failed topost the submission - ${e}`);
+ throw e;
+ }
+ },
async getEmployeeCountRanges() {
try{
const resp = await apiAxios.get(ApiRoutes.EMPLOYEE_COUNT_RANGES);
diff --git a/frontend/src/utils/constant.js b/frontend/src/utils/constant.js
index bb4383013..4af0c7c18 100644
--- a/frontend/src/utils/constant.js
+++ b/frontend/src/utils/constant.js
@@ -24,7 +24,8 @@ export const ApiRoutes = Object.freeze({
fileUpload: {
BASE_URL: fileUploadRoot,
},
- EMPLOYEE_COUNT_RANGES: baseRoot + "/v1/codes/employee-count-ranges"
+ EMPLOYEE_COUNT_RANGES: baseRoot + "/v1/codes/employee-count-ranges",
+ POST_SUBMISSION: baseRoot + "/v1/file-upload",
});
export const PAGE_TITLES = Object.freeze({
From 34217b3b3e5e036a7048fc8802b7f11ea5890bc7 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 17:12:15 -0800
Subject: [PATCH 24/49] added form validation.
---
frontend/src/components/InputForm.vue | 78 +++++++++++++++++++--------
1 file changed, 57 insertions(+), 21 deletions(-)
diff --git a/frontend/src/components/InputForm.vue b/frontend/src/components/InputForm.vue
index 3eddd2a04..5a99df5c1 100644
--- a/frontend/src/components/InputForm.vue
+++ b/frontend/src/components/InputForm.vue
@@ -1,10 +1,8 @@
-
- {{ alertMessage }}
-
-
-
+
+
+
@@ -69,29 +67,33 @@
required disabled>
-
-
+
+
+
-
+
+
-
+
+
-
+
+
@@ -106,9 +108,12 @@
(bc-pay-transparency-tool-data-template.csv) for accurate processing.
-
+
+
+
+
Supported format: CSV. Maximum file size: xxMB.
@@ -118,10 +123,23 @@
-
+
+
+
+ Please complete all required fields
+
-
+
+
+
+
+
+
+
+
+ {{ alertMessage }}
+
@@ -149,12 +167,11 @@ export default {
VueDatePicker
},
data: () => ({
- validForm: false,
+ validForm: null,
requiredRules: [v => !!v || 'Required'],
companyName: '',
companyAddress: '',
naicsCode: null,
- naicsCodeList: ["2342"],
employeeCountRange: null,
isProcessing: false,
uploadFileValue: null,
@@ -187,12 +204,22 @@ export default {
this.isProcessing = true;
console.log('generate report');
try {
- const response = await ApiService.apiAxios.post('/api/v1/file-upload', {
+ const formData = new FormData();
+ formData.append('companyName', this.companyName);
+ formData.append('companyAddress', this.companyAddress);
+ formData.append('naicsCode', this.naicsCode);
+ formData.append('employeeCountRange', this.employeeCountRange);
+ formData.append('startDate', this.startDate);
+ formData.append('endDate', this.endDate);
+ formData.append('comments', this.comments);
+ formData.append('file', this.uploadFileValue[0]);
+ const oldBody = {
companyName: this.companyName,
companyAddress: this.companyAddress,
employeeCount: this.employeeCount,
file: this.uploadFileValue[0],
- });
+ }
+ const response = await ApiService.postSubmission(formData);
console.log(response);
this.setSuccessAlert('Report generated successfully');
this.isProcessing = false;
@@ -266,6 +293,15 @@ export default {
fromDateDisp() {
return this.fromDateVal;
},
+ areRequiredFieldsComplete() {
+ return !!this.companyName &&
+ !!this.companyAddress &&
+ !!this.naicsCode &&
+ !!this.employeeCountRange &&
+ !!this.startDate &&
+ !!this.endDate &&
+ !!this.uploadFileValue
+ }
}
};
From 239fcfb905204961da945153b6713e1fbcb48258 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 17:16:05 -0800
Subject: [PATCH 25/49] added 'multer' library to support unpacking of
multipart form data
---
backend/package-lock.json | 125 +++++++++++++++++++++++++++++++++++++-
backend/package.json | 1 +
2 files changed, 124 insertions(+), 2 deletions(-)
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 214009b68..3cbdeef5d 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -29,6 +29,7 @@
"memory-cache": "^0.2.0",
"moment": "^2.29.4",
"morgan": "^1.10.0",
+ "multer": "^1.4.5-lts.1",
"nconf": "^0.12.0",
"nocache": "^3.0.4",
"passport": "^0.6.0",
@@ -1755,6 +1756,11 @@
"node": ">= 8"
}
},
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
+ },
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
@@ -2059,8 +2065,7 @@
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
- "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
- "dev": true
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/buffer-writer": {
"version": "2.0.0",
@@ -2070,6 +2075,17 @@
"node": ">=4"
}
},
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -2427,6 +2443,47 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "engines": [
+ "node >= 0.8"
+ ],
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "node_modules/concat-stream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/concat-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/concat-stream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -2471,6 +2528,11 @@
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
"dev": true
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+ },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -3524,6 +3586,11 @@
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -4908,6 +4975,25 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
@@ -4947,6 +5033,23 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
+ "node_modules/multer": {
+ "version": "1.4.5-lts.1",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
+ "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.0.0",
+ "concat-stream": "^1.5.2",
+ "mkdirp": "^0.5.4",
+ "object-assign": "^4.1.1",
+ "type-is": "^1.6.4",
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5589,6 +5692,11 @@
"node": ">=16.13"
}
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -6155,6 +6263,14 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -6706,6 +6822,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+ },
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
diff --git a/backend/package.json b/backend/package.json
index a09b8d469..d6f094f65 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -24,6 +24,7 @@
"memory-cache": "^0.2.0",
"moment": "^2.29.4",
"morgan": "^1.10.0",
+ "multer": "^1.4.5-lts.1",
"nconf": "^0.12.0",
"nocache": "^3.0.4",
"passport": "^0.6.0",
From 751e4c707c46fe146e62470d07fbaac318c2132b Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Fri, 10 Nov 2023 17:17:23 -0800
Subject: [PATCH 26/49] work-in-progress commit to start unpacking and
validating the submission
---
backend/src/v1/routes/file-upload-routes.ts | 26 ++++++++++++++-------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/backend/src/v1/routes/file-upload-routes.ts b/backend/src/v1/routes/file-upload-routes.ts
index 173147c40..ec43d3fe8 100644
--- a/backend/src/v1/routes/file-upload-routes.ts
+++ b/backend/src/v1/routes/file-upload-routes.ts
@@ -1,16 +1,24 @@
-import {auth} from "../services/auth-service";
import express from "express";
import passport from 'passport';
+import { auth } from "../services/auth-service";
+import { getCompanies } from '../services/file-upload-service';
+const multer = require('multer');
+const upload = multer();
const fileUploadRouter = express.Router();
-import {getCompanies} from '../services/file-upload-service';
-fileUploadRouter.post("/",passport.authenticate('jwt', {session: false}, undefined), auth.isValidBackendToken(), async (req, res) => {
- //await saveFileUpload(req.body);
- console.log(req.body);
- res.sendStatus(200);
-});
-fileUploadRouter.get("/",passport.authenticate('jwt', {session: false}, undefined), auth.isValidBackendToken(), async (req, res) => {
+fileUploadRouter.post("/",
+ passport.authenticate('jwt', { session: false }, undefined),
+ auth.isValidBackendToken(),
+ upload.single("file"),
+ async (req, res) => {
+ //await saveFileUpload(req.body);
+ console.log("submission body:");
+ console.log(req.body);
+ res.sendStatus(200);
+ });
+fileUploadRouter.get("/", passport.authenticate('jwt', { session: false }, undefined), auth.isValidBackendToken(), async (req, res) => {
const companies = await getCompanies();
res.status(200).json(companies);
});
-export {fileUploadRouter};
+export { fileUploadRouter };
+
From cc827780733f18874117045516f518e07bc300dc Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 13 Nov 2023 10:40:32 -0800
Subject: [PATCH 27/49] added csv-parse library as a dependency
---
backend/package-lock.json | 6 ++++++
backend/package.json | 1 +
2 files changed, 7 insertions(+)
diff --git a/backend/package-lock.json b/backend/package-lock.json
index 3cbdeef5d..5585dea09 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -15,6 +15,7 @@
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"cron": "^2.3.0",
+ "csv-parse": "^5.5.2",
"dotenv": "^16.0.3",
"easy-soap-request": "^5.4.0",
"express": "^4.18.2",
@@ -2573,6 +2574,11 @@
"node": ">= 8"
}
},
+ "node_modules/csv-parse": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.2.tgz",
+ "integrity": "sha512-YRVtvdtUNXZCMyK5zd5Wty1W6dNTpGKdqQd4EQ8tl/c6KW1aMBB1Kg1ppky5FONKmEqGJ/8WjLlTNLPne4ioVA=="
+ },
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
diff --git a/backend/package.json b/backend/package.json
index d6f094f65..f79d2b69e 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -10,6 +10,7 @@
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"cron": "^2.3.0",
+ "csv-parse": "^5.5.2",
"dotenv": "^16.0.3",
"easy-soap-request": "^5.4.0",
"express": "^4.18.2",
From 330c2e90eba2532e6f09fb59bb5455450788b2a0 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 13 Nov 2023 10:41:09 -0800
Subject: [PATCH 28/49] new service with a group of functions to support
validation of uploaded files and other form fields
---
backend/src/v1/services/validate-service.ts | 227 ++++++++++++++++++++
1 file changed, 227 insertions(+)
create mode 100644 backend/src/v1/services/validate-service.ts
diff --git a/backend/src/v1/services/validate-service.ts b/backend/src/v1/services/validate-service.ts
new file mode 100644
index 000000000..3edd38c9f
--- /dev/null
+++ b/backend/src/v1/services/validate-service.ts
@@ -0,0 +1,227 @@
+import { parse } from 'csv-parse/sync';
+import { logger as log } from '../../logger';
+
+const COL_GENDER_CODE = "Gender Code";
+const COL_HOURS_WORKED = "Hours Worked";
+const COL_REGULAR_SALARY = "Regular Salary";
+const COL_SPECIAL_SALARY = "Special Salary";
+const COL_OVERTIME_HOURS = "Overtime Hours";
+const COL_OVERTIME_PAY = "Overtime Pay";
+const COL_BONUS_PAY = "Bonus Pay";
+const COL_REGULAR_HOURLY_WAGE = "Regular Hourly Wage";
+const EXPECTED_COLUMNS: string[] = [
+ COL_GENDER_CODE,
+ COL_HOURS_WORKED,
+ COL_REGULAR_SALARY,
+ COL_SPECIAL_SALARY,
+ COL_OVERTIME_HOURS,
+ COL_OVERTIME_PAY,
+ COL_BONUS_PAY,
+ COL_REGULAR_HOURLY_WAGE
+];
+const NUMERIC_COLUMNS = [
+ COL_HOURS_WORKED,
+ COL_REGULAR_SALARY,
+ COL_SPECIAL_SALARY,
+ COL_OVERTIME_HOURS,
+ COL_OVERTIME_PAY,
+ COL_BONUS_PAY,
+ COL_REGULAR_HOURLY_WAGE
+];
+const INVALID_COLUMN_ERROR = "Invalid CSV format. Please ensure the uploaded file contains the following columns: " + EXPECTED_COLUMNS.join(",")
+const GENDER_CODES = ["M", "F", "X", "U", "W"];
+const ZERO_SYNONYMS = ["", "N/A"]
+
+interface Row {
+ record: any;
+ raw: string
+}
+
+const validateService = {
+
+ /*
+ Validates the content of the submission body, which includes all form fields,
+ but excludes the uploaded CSV file.
+ */
+ validateBody(body: string): string[] {
+ return [];
+ },
+
+ /*
+ Validates all fields in the submission, including the form fields and the
+ uploaded file. Returns an array of error messages if any errors
+ are found, or returns an empty array if no errors are found (if the
+ submission is valid)
+ */
+ validateCsv(csvContent: string): string[] {
+
+ const errorsToReturn: string[] = [];
+
+ // Parse the CSV content and check that the column names as
+ // expected (and in the expected order)
+ var rows: Row[] = [];
+ try {
+ rows = this.parseCsv(csvContent)
+ }
+ catch (e) {
+ errorsToReturn.push(e.message);
+ }
+
+ // If there were errors during the first stages of validation then
+ // return those errors now without doing any further validation.
+ if (errorsToReturn?.length) {
+ return errorsToReturn;
+ }
+
+ // Scan each row, checking that they all have valid content in all columns
+ const rowErrors = this.validateRows(rows);
+ if (rowErrors?.length) {
+ errorsToReturn.push(...rowErrors);
+ }
+
+ return errorsToReturn;
+ },
+
+ /*
+ Convert the CSV file into an array of objects. Throw an error if any problems occur.
+ If there are no problems with the parsing, each resulting object will include both the
+ translated version of the CSV row as a javascript object and also the original (raw)
+ version of the CSV row as a string.
+
+ For example, given this CSV row:
+ F,1853,85419.00,,7,484.03,,46.10
+
+ The resulting object would be:
+ {
+ record: {
+ 'Gender Code': 'F',
+ 'Hours Worked': '1853',
+ 'Regular Salary': '85419.00',
+ 'Special Salary': '',
+ 'Overtime Hours': '7',
+ 'Overtime Pay': '484.03',
+ 'Bonus Pay': '',
+ 'Regular Hourly Wage': '46.10'
+ },
+ raw: 'F,1853,85419.00,,7,484.03,,46.10\r'
+ }
+
+ */
+ parseCsv(csvContent: string): Row[] {
+ var rows = [];
+ try {
+ rows = parse(csvContent, {
+ columns: true,
+ bom: true,
+ raw: true,
+ trim: true,
+ ltrim: true,
+ skip_empty_lines: true,
+ relax_column_count: true
+ });
+ }
+ catch (e) {
+ log.debug(e);
+ throw new Error("Unable to parse file");
+ }
+
+ if (!rows?.length) {
+ throw new Error("No content");
+ }
+
+ // Confirm that the CSV contains the expected columns in the expected order
+ const firstRow = rows[0];
+ const colNames = Object.getOwnPropertyNames(firstRow.record);
+ if (colNames?.length != EXPECTED_COLUMNS.length) {
+ throw new Error(INVALID_COLUMN_ERROR);
+ }
+ for (var i = 0; i < colNames.length; i++) {
+ if (colNames[i] != EXPECTED_COLUMNS[i]) {
+ throw new Error(INVALID_COLUMN_ERROR);
+ }
+ }
+
+ return rows;
+ },
+
+ /*
+ Scans all rows. For each row check that all columns have valid values.
+ Return an array of any row errors found
+ */
+ validateRows(rows: Row[]): string[] {
+ const allRowErrors: string[] = [];
+ for (var rowNum = 0; rowNum < rows.length; rowNum++) {
+ const lineNum = rowNum + 1;
+ const row = rows[rowNum];
+ const errorsForCurrentRow = this.validateRow(lineNum, row);
+ if (errorsForCurrentRow?.length) {
+ const rowError = `Line ${lineNum}: ${errorsForCurrentRow.join(" ")}`
+ allRowErrors.push(rowError);
+ }
+ }
+ return allRowErrors;
+ },
+
+ /*
+ Scans the given row. Check that all columns have valid values.
+ Return an array of any errors in the given row
+ */
+ validateRow(rowNum: number, row: Row): string[] {
+ const record = row.record;
+ const errors: string[] = [];
+ if (GENDER_CODES.indexOf(record[COL_GENDER_CODE]) == -1) {
+ errors.push(`Invalid ${COL_GENDER_CODE} '${record[COL_GENDER_CODE]}' (expected one of: ${GENDER_CODES}).`)
+ }
+ NUMERIC_COLUMNS.forEach(colName => {
+ if (!this.isZeroSynonym(record[colName]) && !this.isValidNumber(record[colName])) {
+ errors.push(`Invalid number '${record[colName]}' in ${colName}.`)
+ }
+ })
+
+ return errors;
+ },
+
+ /*
+ Returns true if the given value is one of the accepted synonyms meaning
+ zero, otherwise returns false.
+ */
+ isZeroSynonym(val: any): boolean {
+ if (typeof val == "string") {
+ val = val.toUpperCase();
+ }
+ return ZERO_SYNONYMS.indexOf(val) >= 0;
+ },
+
+ isValidNumber(val: any, min: number = null, max: number = null): boolean {
+ if (val === null || isNaN(val)) {
+ console.log("null value");
+ return false
+ }
+
+ // Check that the value is either a number or a string that parses
+ // to a number. Integer and float are both allowed.
+ var num: number;
+ try {
+ num = parseFloat(val);
+ }
+ catch (e) {
+ console.log(e);
+ return false;
+ }
+
+ // If the minimum or maximum were specified, validate against those
+ if (min !== null && num < min) {
+ return false;
+ }
+ if (max !== null && num > max) {
+ return false;
+ }
+
+ return true;
+
+ }
+
+}
+
+export { validateService };
+
From 49ea385089db61e796b98ca0a5b665bc200c648c Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 13 Nov 2023 10:42:24 -0800
Subject: [PATCH 29/49] implemented basic csv file validation
---
backend/src/v1/routes/file-upload-routes.ts | 32 +++++++++++++++++++--
1 file changed, 30 insertions(+), 2 deletions(-)
diff --git a/backend/src/v1/routes/file-upload-routes.ts b/backend/src/v1/routes/file-upload-routes.ts
index ec43d3fe8..8cd629c01 100644
--- a/backend/src/v1/routes/file-upload-routes.ts
+++ b/backend/src/v1/routes/file-upload-routes.ts
@@ -2,9 +2,17 @@ import express from "express";
import passport from 'passport';
import { auth } from "../services/auth-service";
import { getCompanies } from '../services/file-upload-service';
+import { validateService } from "../services/validate-service";
const multer = require('multer');
const upload = multer();
+interface ValidationErrorResponse {
+ status: string;
+ body_errors: string[];
+ file_errors: string[];
+ general_errors: string[];
+}
+
const fileUploadRouter = express.Router();
fileUploadRouter.post("/",
passport.authenticate('jwt', { session: false }, undefined),
@@ -12,9 +20,29 @@ fileUploadRouter.post("/",
upload.single("file"),
async (req, res) => {
//await saveFileUpload(req.body);
- console.log("submission body:");
- console.log(req.body);
+
+ try {
+ const bodyErrors = validateService.validateBody(req.body);
+ const fileErrors = validateService.validateCsv(req.file.buffer);
+ if (bodyErrors?.length || fileErrors?.length) {
+ res.status(400).json({
+ status: "error",
+ body_errors: bodyErrors,
+ file_errors: fileErrors
+ } as ValidationErrorResponse)
+ return;
+ }
+ }
+ catch (e) {
+ res.status(500).json({
+ status: "error",
+ general_errors: ["Something went wrong"]
+ } as ValidationErrorResponse)
+ return;
+ }
+
res.sendStatus(200);
+
});
fileUploadRouter.get("/", passport.authenticate('jwt', { session: false }, undefined), auth.isValidBackendToken(), async (req, res) => {
const companies = await getCompanies();
From f100680042a407166e6057c6a364d684a8cbbc4d Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 13 Nov 2023 12:12:48 -0800
Subject: [PATCH 30/49] changed format of error response if validation errors
are found
---
backend/src/v1/routes/file-upload-routes.ts | 22 ++++--
backend/src/v1/services/validate-service.ts | 83 ++++++++++++++-------
2 files changed, 71 insertions(+), 34 deletions(-)
diff --git a/backend/src/v1/routes/file-upload-routes.ts b/backend/src/v1/routes/file-upload-routes.ts
index 8cd629c01..372ed9f2f 100644
--- a/backend/src/v1/routes/file-upload-routes.ts
+++ b/backend/src/v1/routes/file-upload-routes.ts
@@ -2,15 +2,17 @@ import express from "express";
import passport from 'passport';
import { auth } from "../services/auth-service";
import { getCompanies } from '../services/file-upload-service';
-import { validateService } from "../services/validate-service";
+import { FileErrors, validateService } from "../services/validate-service";
const multer = require('multer');
const upload = multer();
interface ValidationErrorResponse {
status: string;
- body_errors: string[];
- file_errors: string[];
- general_errors: string[];
+ errors: {
+ bodyErrors: string[];
+ fileErrors: FileErrors | null;
+ generalErrors: string[];
+ }
}
const fileUploadRouter = express.Router();
@@ -24,11 +26,13 @@ fileUploadRouter.post("/",
try {
const bodyErrors = validateService.validateBody(req.body);
const fileErrors = validateService.validateCsv(req.file.buffer);
- if (bodyErrors?.length || fileErrors?.length) {
+ if (bodyErrors?.length || fileErrors) {
res.status(400).json({
status: "error",
- body_errors: bodyErrors,
- file_errors: fileErrors
+ errors: {
+ bodyErrors: bodyErrors,
+ fileErrors: fileErrors
+ }
} as ValidationErrorResponse)
return;
}
@@ -36,7 +40,9 @@ fileUploadRouter.post("/",
catch (e) {
res.status(500).json({
status: "error",
- general_errors: ["Something went wrong"]
+ errors: {
+ generalErrors: ["Something went wrong"]
+ }
} as ValidationErrorResponse)
return;
}
diff --git a/backend/src/v1/services/validate-service.ts b/backend/src/v1/services/validate-service.ts
index 3edd38c9f..889532717 100644
--- a/backend/src/v1/services/validate-service.ts
+++ b/backend/src/v1/services/validate-service.ts
@@ -30,13 +30,23 @@ const NUMERIC_COLUMNS = [
];
const INVALID_COLUMN_ERROR = "Invalid CSV format. Please ensure the uploaded file contains the following columns: " + EXPECTED_COLUMNS.join(",")
const GENDER_CODES = ["M", "F", "X", "U", "W"];
-const ZERO_SYNONYMS = ["", "N/A"]
+const ZERO_SYNONYMS = ["N/A"]
interface Row {
record: any;
raw: string
}
+interface FileErrors {
+ generalErrors: string[],
+ lineErrors: LineErrors[]
+}
+
+interface LineErrors {
+ lineNum: number,
+ errors: string[]
+}
+
const validateService = {
/*
@@ -53,9 +63,12 @@ const validateService = {
are found, or returns an empty array if no errors are found (if the
submission is valid)
*/
- validateCsv(csvContent: string): string[] {
+ validateCsv(csvContent: string): FileErrors {
- const errorsToReturn: string[] = [];
+ const fileErrors: FileErrors = {
+ generalErrors: null,
+ lineErrors: null
+ }
// Parse the CSV content and check that the column names as
// expected (and in the expected order)
@@ -64,22 +77,26 @@ const validateService = {
rows = this.parseCsv(csvContent)
}
catch (e) {
- errorsToReturn.push(e.message);
+ fileErrors.generalErrors = [e.message];
}
// If there were errors during the first stages of validation then
// return those errors now without doing any further validation.
- if (errorsToReturn?.length) {
- return errorsToReturn;
+ if (fileErrors.generalErrors?.length) {
+ return fileErrors;
}
// Scan each row, checking that they all have valid content in all columns
- const rowErrors = this.validateRows(rows);
- if (rowErrors?.length) {
- errorsToReturn.push(...rowErrors);
+ const lineErrors: LineErrors[] = this.validateRows(rows);
+ if (lineErrors?.length) {
+ fileErrors.lineErrors = lineErrors;
+ }
+
+ if (fileErrors.generalErrors?.length || fileErrors.lineErrors?.length) {
+ return fileErrors
}
- return errorsToReturn;
+ return null;
},
/*
@@ -148,15 +165,18 @@ const validateService = {
Scans all rows. For each row check that all columns have valid values.
Return an array of any row errors found
*/
- validateRows(rows: Row[]): string[] {
- const allRowErrors: string[] = [];
+ validateRows(rows: Row[]): LineErrors[] {
+ const allRowErrors: LineErrors[] = [];
for (var rowNum = 0; rowNum < rows.length; rowNum++) {
- const lineNum = rowNum + 1;
+
+ //+1 because the line numbers are not zero-indexed, and
+ //+1 again because the first data line is actually the second line of the file(after the header line)
+ const lineNum = rowNum + 2;
+
const row = rows[rowNum];
- const errorsForCurrentRow = this.validateRow(lineNum, row);
- if (errorsForCurrentRow?.length) {
- const rowError = `Line ${lineNum}: ${errorsForCurrentRow.join(" ")}`
- allRowErrors.push(rowError);
+ const errorsForCurrentLine: LineErrors = this.validateRow(lineNum, row);
+ if (errorsForCurrentLine) {
+ allRowErrors.push(errorsForCurrentLine);
}
}
return allRowErrors;
@@ -164,21 +184,30 @@ const validateService = {
/*
Scans the given row. Check that all columns have valid values.
- Return an array of any errors in the given row
+ Return a LineErrors object if any errors are found, or returns null
+ if no errors are found
*/
- validateRow(rowNum: number, row: Row): string[] {
+ validateRow(lineNum: number, row: Row): LineErrors {
const record = row.record;
- const errors: string[] = [];
+ const errorMessages: string[] = [];
if (GENDER_CODES.indexOf(record[COL_GENDER_CODE]) == -1) {
- errors.push(`Invalid ${COL_GENDER_CODE} '${record[COL_GENDER_CODE]}' (expected one of: ${GENDER_CODES}).`)
+ errorMessages.push(`Invalid ${COL_GENDER_CODE} '${record[COL_GENDER_CODE]}' (expected one of: ${GENDER_CODES}).`)
}
NUMERIC_COLUMNS.forEach(colName => {
- if (!this.isZeroSynonym(record[colName]) && !this.isValidNumber(record[colName])) {
- errors.push(`Invalid number '${record[colName]}' in ${colName}.`)
+ if (!this.isZeroSynonym(record[colName]) && !this.isValidNumber(record[colName], 0)) {
+ errorMessages.push(`Invalid number '${record[colName]}' in ${colName}.`)
}
})
- return errors;
+ if (errorMessages.length) {
+ const lineErrors = {
+ lineNum: lineNum,
+ errors: errorMessages
+ }
+ return lineErrors;
+ }
+
+ return null;
},
/*
@@ -194,7 +223,6 @@ const validateService = {
isValidNumber(val: any, min: number = null, max: number = null): boolean {
if (val === null || isNaN(val)) {
- console.log("null value");
return false
}
@@ -223,5 +251,8 @@ const validateService = {
}
-export { validateService };
+export {
+ FileErrors,
+ validateService
+};
From 374a58827b8e39e5ab087de374a84811a7accfc3 Mon Sep 17 00:00:00 2001
From: Brock Anderson
Date: Mon, 13 Nov 2023 12:13:31 -0800
Subject: [PATCH 31/49] display validation errors if any are returned
---
frontend/src/components/InputForm.vue | 85 +++++++++++++++++++++++----
1 file changed, 73 insertions(+), 12 deletions(-)
diff --git a/frontend/src/components/InputForm.vue b/frontend/src/components/InputForm.vue
index 5a99df5c1..e3111da65 100644
--- a/frontend/src/components/InputForm.vue
+++ b/frontend/src/components/InputForm.vue
@@ -135,14 +135,58 @@
-
+
-
+
{{ alertMessage }}
+
+
+
+ The uploaded CSV file is invalid. Please see below for an explanation.
+
+
+
+
+
+ {{ generalError }}
+ |
+
+
+
+
+ Problems were found on the following lines.
+
+
+
+
+
+ Line
+ |
+
+ Problem(s)
+ |
+
+
+
+
+ {{ lineError.lineNum }} |
+
+
+ {{ errMsg }}
+
+ |
+
+
+
+
+
+
+
+
@@ -150,7 +194,7 @@
-