From 6cd65b4a7ea40d1090afb48066e1407876097abb Mon Sep 17 00:00:00 2001 From: Paulius Date: Fri, 6 Sep 2024 13:38:09 +0100 Subject: [PATCH 1/5] Feature/vr 175 (#138) * changes + fixed most existing test * added test * updated tests before including DRPC * comment * newQueryError * updated tests etc * fixed tests * failing test * drcpEvents tests * updated test * updated test * new test * new snaps * updates * updated * updates * VR-175: ground work for partial queries. * VR-175: rebasing. * VR-175: before merge. * VR-175: after merge. * VR-175: test update. * VR-175: minor updates to form's payload after manual testing with multiple. * VR-175: adding error responses for partial query controller. * VR-175: some scenarios for queries controller. * VR-175: typo in a unit test. * VR-175: forgotten snapshot. * VR-175: github comments part #1. * VR-175: some more controller tests. * VR-175: moving files. * VR-175: increased logging. * VR-175: v-bump. * VR-175: set DEMO_MODE to true in docker compose. * VR-175: empty file. * VR-175: moving files and functions around. * VR-175: readme update. * VR-175: version bump. * VR-175: moving files part #2, combining scope3 views. * VR-175: linting. * VR-175: leftovers reset.css -> main.css. * VR-175: version bump. * VR-175: comments. * VR-175: another rename/file move. * VR-175: linting. * VR-175: and another test scenario for partial = false. * VR-175: remove companyId in partial controller. * VR-175: version bump. * VR-175: linting. * VR-175: typo --------- Co-authored-by: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> --- README.md | 1 + docker-compose.yml | 1 + package-lock.json | 4 +- package.json | 2 +- public/images/tick-white.svg | 5 + public/styles/main.css | 128 +++++-- public/styles/reset.css | 1 + public/styles/shared.css | 2 +- src/controllers/__tests__/auth.test.ts | 3 +- src/controllers/queries/__tests__/helpers.ts | 62 +++- .../queries/__tests__/index.test.ts | 90 ++++- src/controllers/queries/index.ts | 122 ++++--- src/views/common.tsx | 4 +- .../__tests__/fromInvite.test.ts.snap | 10 - .../__tests__/pinSubmission.test.ts.snap | 10 - .../queries/__tests__/partial-query.test.ts | 70 ++++ .../__tests__/partial-query.test.ts.snap | 5 + .../__tests__/queriesList.test.ts.snap | 2 +- .../queryResponsesTests/scope3.test.ts | 117 ------- .../__tests__/requestCo2scope3.test.ts} | 2 +- .../__tests__/requestCo2scope3.test.ts.snap} | 4 +- .../__tests__/responseCo2scope3.test.ts | 83 +++++ .../__tests__/responseCo2scope3.test.ts.snap | 13 + src/views/queries/queriesList.tsx | 8 +- .../requestCo2scope3.tsx} | 12 +- src/views/queries/responseCo2scope3.tsx | 322 ++++++++++++++++++ 26 files changed, 836 insertions(+), 247 deletions(-) create mode 100644 public/images/tick-white.svg create mode 100644 src/views/queries/__tests__/partial-query.test.ts create mode 100644 src/views/queries/__tests__/partial-query.test.ts.snap delete mode 100644 src/views/queries/__tests__/queryResponsesTests/scope3.test.ts rename src/views/{queryTypes/__tests__/scope3.test.ts => queries/__tests__/requestCo2scope3.test.ts} (97%) rename src/views/{queryTypes/__tests__/scope3.test.ts.snap => queries/__tests__/requestCo2scope3.test.ts.snap} (85%) create mode 100644 src/views/queries/__tests__/responseCo2scope3.test.ts create mode 100644 src/views/queries/__tests__/responseCo2scope3.test.ts.snap rename src/views/{queryTypes/scope3.tsx => queries/requestCo2scope3.tsx} (97%) create mode 100644 src/views/queries/responseCo2scope3.tsx diff --git a/README.md b/README.md index 98d998a5..6c325f57 100644 --- a/README.md +++ b/README.md @@ -110,6 +110,7 @@ This is the list of all environment variables including brief description | ISSUANCE_DID_POLICY | y | EXISTING_OR_NEW | DID and either create or use existing: [CREATE_NEW, FIND_EXIStING, EXISTING_OR_NEW, did:somedid] | | ISSUANCE_SCHEMA_POLICY | y | EXISTING_OR_NEW | Same as above but for credential schema | | ISSUANCE_CRED_DEF_POLICY | y | EXISTING_OR_NEW | Same as above but this is for credential definitions | +| DEMO_MODE | y | true | Enables or disables the `/reset` endpoint ## Testing Currently this repository consist of two test types: [**integration**, **unit**] and we are using a combination of `mocha`, `chai` and `sinon` frameworks diff --git a/docker-compose.yml b/docker-compose.yml index a12ca8dd..d5850c32 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -96,6 +96,7 @@ services: - ISSUANCE_DID_POLICY=EXISTING_OR_NEW - ISSUANCE_SCHEMA_POLICY=EXISTING_OR_NEW - ISSUANCE_CRED_DEF_POLICY=EXISTING_OR_NEW + - DEMO_MODE=true postgres-veritable-ui-bob: image: postgres:16.4-alpine container_name: postgres-veritable-ui-bob diff --git a/package-lock.json b/package-lock.json index 1baa90a8..895de3fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "veritable-ui", - "version": "0.8.50", + "version": "0.8.51", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "veritable-ui", - "version": "0.8.50", + "version": "0.8.51", "license": "Apache-2.0", "dependencies": { "@digicatapult/tsoa-oauth-express": "^0.1.38", diff --git a/package.json b/package.json index 9e94d37b..990348fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "veritable-ui", - "version": "0.8.50", + "version": "0.8.51", "description": "UI for Veritable", "main": "src/index.ts", "type": "module", diff --git a/public/images/tick-white.svg b/public/images/tick-white.svg new file mode 100644 index 00000000..91bceb63 --- /dev/null +++ b/public/images/tick-white.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/styles/main.css b/public/styles/main.css index 0e6f05b5..03cd3142 100644 --- a/public/styles/main.css +++ b/public/styles/main.css @@ -26,6 +26,7 @@ --bg-color: #f4f5fb; --secondary-color: #ffcc91; --accent-color: #5670f1; + --focus-color: rgba(86, 112, 241, 0.6); --border-black: #cfd3d4; --neutral-accent: var(--accent-color); @@ -285,17 +286,15 @@ main { .list-page { background-color: #fff; overflow-x: auto; - border-radius: 12px; padding: 1rem; } .card-body { background-color: #fff; - overflow-x: auto; - max-width: 1000px; + border: 2px solid #5570f1; border-radius: 12px; padding: 2rem 2rem; - margin: 1rem 2rem 0rem 1rem; + margin: 1rem; } .spinner { @@ -316,7 +315,7 @@ main { border-radius: 5px; border-width: 1px; padding: 10px; - background-image: url('../images/search.svg'); + background-image: url('/public/images/search.svg'); background-repeat: no-repeat; background-position: 10px; padding-left: 40px; @@ -365,11 +364,13 @@ main { border: 1px solid lightgray; } } + .center-category { display: flex; justify-content: center; align-items: center; } + .category-header { text-align: center; font-size: 2rem; @@ -377,15 +378,18 @@ main { margin: 0px; font-weight: bold; } + .category-text { font-size: 0.875rem; text-align: left; } + .category-align-in-row { display: flex; flex-direction: row; align-items: left; } + .category-icon { padding-top: 10px; height: 41px; @@ -400,6 +404,7 @@ main { height: 100%; cursor: pointer; } + .query-item { background-color: #fff; overflow-x: auto; @@ -419,12 +424,14 @@ main { border: 1px solid lightslategrey; } } + .query-header { text-align: left; font-size: 2rem; padding-top: 10px; margin: 0px; } + .query-text { font-size: 0.875rem; padding-right: 15px; @@ -432,16 +439,18 @@ main { position: absolute; top: 55%; } + .query-item:hover .query-header, .query-item:hover .query-text { color: var(--bg-color); } .divider { border: 0; - border-top: 2px solid #000; + border-top: 2px solid #100e0e; margin-top: 0.25rem; margin-bottom: 1rem; } + .query-page-header { font-size: 2.5em; font-weight: bold; @@ -450,23 +459,34 @@ main { margin-bottom: 0rem; } +.query-partial-container { + padding: 5px; +} + .container-scope3-carbon { display: flex; flex-direction: row; flex-wrap: wrap; + overflow: hidden; min-height: 30em; } -.box1 { - flex: 0 0 40%; +.scope3-co2-left { + flex: 0.5; + min-height: 425px; padding-right: 20px; } -.box2 { +.scope3-co2-right { + padding-top: 5px; flex: 1; - display: flex; flex-direction: column; } + +.scope3-co2-left #scope3-co2-heading { + margin-bottom: 150px; +} + .box3 { flex: 1; display: flex; @@ -493,9 +513,10 @@ main { flex-direction: column; } -.query-input-field { +.input-with-label { border: 1px solid var(--border-black); box-shadow: none; + font-size: 0.75rem; width: 100%; min-width: 100px; max-width: 250px; @@ -505,13 +526,22 @@ main { border-radius: 0.5em; margin-top: var(--form-element-vert-spacing); } + +.input-basic { + border: 1px solid var(--border-black); + font-size: 0.75rem; + padding: 5px; + border-radius: 0.5em; + background: rgba(233, 236, 248, 1); + margin-top: var(--form-element-vert-spacing); + transition: all 0.3s ease-out; +} + .query-text-carbon3-consumption { - font-size: 0.875rem; - max-width: 20%; + font-size: 1rem; text-align: left; - position: absolute; - top: 55%; } + .input-container { position: relative; margin-bottom: 20px; @@ -521,12 +551,12 @@ main { position: absolute; top: 5px; left: 5px; - background-color: white; padding: 0 5px; font-size: 9px; color: #333; opacity: 0.7; } + .additional-input-label { padding: 0px; margin: 0px; @@ -534,6 +564,7 @@ main { opacity: 0.6; padding-top: 3px; } + #new-query-confirmation-text { font-size: 0.875rem; margin-inline: 40px; @@ -542,16 +573,61 @@ main { flex-direction: column; align-items: center; } + #company-selected-next-button { background-color: var(--accent-color); margin-top: 5px; color: white; } + .warn-pin-attempts { color: var(--negative-accent); font-size: 0.875rem; } +input[type='checkbox'] { + display: inline-flex; + appearance: none; + background-color: #fff; + font: inherit; + color: var(--text-color-secondary); + width: 17px; + height: 17px; + border: 1px solid var(--text-color-secondary); + margin-right: 10px; + border-radius: 5px; + align-items: center; + justify-content: center; + + &:hover { + border: 1px solid var(--accent-color); + background-color: rgba(86, 112, 241, 0.1); + } +} + +input:focus { + outline: 2px solid var(--focus-color); +} + +input:hover { + border: 1px solid var(--accent-color); +} + +input[type='checkbox']::before { + content: ''; + border-radius: 3px; + width: 15px; + height: 15px; + transform: scale(0); + background-color: var(--accent-color); + background-size: cover; + background-image: url('/public/images/tick-white.svg'); +} + +input[type='checkbox']:checked::before { + transform: scale(1); +} + /* Mobile view */ @media (max-width: 767px) { body { @@ -572,12 +648,15 @@ main { align-items: space-around; width: 100%; - justify-content: space-between; + justify-content: center; + gap: 12px; min-height: auto; } #side-bar > img:first-child { - margin: 0; + position: absolute; + left: 0; + top: 10px; } .search-window { @@ -592,14 +671,21 @@ main { .category-container { height: 100%; } - .query-item { + .query-item, + .scope3-co2-left { min-height: 250px; } .query-text-carbon3-consumption { max-width: 100%; - text-align: left; position: relative; - top: auto; + top: 0; + } + .scope3-co2-left #scope3-co2-heading { + text-align: center; + margin-bottom: 0; + } + .scope3-co2-left { + flex: 1; } .max-height-table { max-height: none; diff --git a/public/styles/reset.css b/public/styles/reset.css index 2c4f36cc..d6b93694 100644 --- a/public/styles/reset.css +++ b/public/styles/reset.css @@ -22,6 +22,7 @@ button { color: inherit; font-size: inherit; font-weight: inherit; + transition: all 0.3s; } @media (prefers-reduced-motion: reduce) { diff --git a/public/styles/shared.css b/public/styles/shared.css index 332dab34..944a1a21 100644 --- a/public/styles/shared.css +++ b/public/styles/shared.css @@ -7,7 +7,7 @@ &:hover, &:focus-visible { - filter: blur(3px); + filter: blur(1px); opacity: 1; cursor: not-allowed; } diff --git a/src/controllers/__tests__/auth.test.ts b/src/controllers/__tests__/auth.test.ts index d55e7c89..8cde708a 100644 --- a/src/controllers/__tests__/auth.test.ts +++ b/src/controllers/__tests__/auth.test.ts @@ -258,8 +258,7 @@ describe('AuthController', () => { await controller.redirect( { ...req, signedCookies: { 'VERITABLE_NONCE.suffix': 'nonce' } } as unknown as express.Request, 'suffix.nonce', - undefined, - 'error' + undefined ) const stub = req.res.redirect diff --git a/src/controllers/queries/__tests__/helpers.ts b/src/controllers/queries/__tests__/helpers.ts index 3c2efa26..0479af90 100644 --- a/src/controllers/queries/__tests__/helpers.ts +++ b/src/controllers/queries/__tests__/helpers.ts @@ -1,15 +1,16 @@ import { Readable } from 'node:stream' import { pino } from 'pino' import sinon from 'sinon' + import { ILogger } from '../../../logger.js' import Database from '../../../models/db/index.js' import { ConnectionRow, QueryRow } from '../../../models/db/types.js' +import { UUID } from '../../../models/strings.js' import VeritableCloudagent from '../../../models/veritableCloudagent.js' import QueriesTemplates from '../../../views/queries/queries.js' import QueryListTemplates from '../../../views/queries/queriesList.js' -import Scope3CarbonConsumptionResponseTemplates from '../../../views/queries/queryResponses/respondToScope3.js' -import Scope3CarbonConsumptionViewResponseTemplates from '../../../views/queries/queryResponses/viewResponseToScope3.js' -import Scope3CarbonConsumptionTemplates from '../../../views/queryTypes/scope3.js' +import Scope3CarbonConsumptionTemplates from '../../../views/queries/requestCo2scope3.js' +import Scope3CarbonConsumptionResponseTemplates, { Scope3FormProps } from '../../../views/queries/responseCo2scope3.js' type QueryStatus = 'resolved' | 'pending_your_input' | 'pending_their_input' @@ -26,14 +27,48 @@ type QueryMockOptions = { query: Partial[] } } + +const mockIds: { [k: string]: UUID } = { + queryId: '00000000-0000-0000-0000-d8ae0805059e', + companyId: 'cccccccc-0001-0000-0000-d8ae0805059e', + connectionId: 'cccccccc-0000-0000-0000-d8ae0805059e', + agentConnectionId: 'aaaaaaaa-0000-0000-0000-d8ae0805059e', +} + const defaultOptions: QueryMockOptions = { getRows: { - connection: [{ company_name: 'VER123', status: 'verified_both', id: '11', agent_connection_id: 'agentId' }], + connection: [ + { + company_name: 'VER123', + status: 'verified_both', + id: mockIds.companyId, + agent_connection_id: mockIds.agentConnectionId, + }, + { + company_name: 'PARTIAL_QUERY', + status: 'verified_both', + id: mockIds.connectionId, + agent_connection_id: mockIds.agentConnectionId, + }, + { + company_name: 'VERIFIED_THEM', + status: 'verified_them', + id: mockIds.connectionId, + agent_connection_id: mockIds.agentConnectionId, + }, + ], query: [ { - id: 'x', + id: mockIds.queryId, status: 'pending_their_input', - connection_id: '11', + connection_id: mockIds.companyId, + details: { quantity: 2, queryId: 'xyz123' }, + response_id: '5390af91-c551-4d74-b394-d8ae0805059e', + }, + { + id: mockIds.queryId, + status: 'pending_your_input', + connection_id: mockIds.connectionId, details: { quantity: 2, queryId: 'xyz123' }, response_id: '5390af91-c551-4d74-b394-d8ae0805059e', }, @@ -41,7 +76,8 @@ const defaultOptions: QueryMockOptions = { }, } -function templateFake(templateName: string) { +function templateFake(templateName: string, props?: Scope3FormProps) { + if (props?.partial) return Promise.resolve(`${templateName}_template-${JSON.stringify(props)}`) return Promise.resolve(`${templateName}_template`) } function templateListFake(templateName: string, ...args: unknown[]) { @@ -56,12 +92,10 @@ export const withQueriesMocks = (testOptions: Partial = {}) => const scope3CarbonConsumptionTemplateMock = { newScope3CarbonConsumptionFormPage: (props: { formStage: string }) => templateListFake('scope3', props.formStage), } as unknown as Scope3CarbonConsumptionTemplates - const scope3CarbonConsumptionViewResponseTemplates = { - scope3CarbonConsumptionViewResponsePage: () => templateListFake('scope3Response'), - } as unknown as Scope3CarbonConsumptionViewResponseTemplates const scope3CarbonConsumptionResponseTemplateMock = { - newScope3CarbonConsumptionResponseFormPage: () => templateFake('queriesResponse'), + newScope3CarbonConsumptionResponseFormPage: (props: Scope3FormProps) => templateFake('queriesResponse', props), + view: () => templateListFake('scope3Response'), } as unknown as Scope3CarbonConsumptionResponseTemplates const queryTemplateMock = { chooseQueryPage: () => templateFake('queries'), @@ -85,7 +119,7 @@ export const withQueriesMocks = (testOptions: Partial = {}) => id: 123, created_at: new Date(), updated_at: new Date(), - connection_id: '11', + connection_id: 'aa000000-0000-0000-0000-aabbccddee00', query_type: 'Scope 3', details: 'some details', }, @@ -95,7 +129,6 @@ export const withQueriesMocks = (testOptions: Partial = {}) => return { scope3CarbonConsumptionTemplateMock, scope3CarbonConsumptionResponseTemplateMock, - scope3CarbonConsumptionViewResponseTemplates, queryListTemplateMock, queryTemplateMock, mockLogger, @@ -104,7 +137,6 @@ export const withQueriesMocks = (testOptions: Partial = {}) => args: [ scope3CarbonConsumptionTemplateMock, scope3CarbonConsumptionResponseTemplateMock, - scope3CarbonConsumptionViewResponseTemplates, queryTemplateMock, queryListTemplateMock, cloudagentMock as unknown as VeritableCloudagent, @@ -121,3 +153,5 @@ export const toHTMLString = async (stream: Readable) => { } return Buffer.concat(chunks).toString('utf8') } + +export { mockIds } diff --git a/src/controllers/queries/__tests__/index.test.ts b/src/controllers/queries/__tests__/index.test.ts index 94ac695a..f518c83d 100644 --- a/src/controllers/queries/__tests__/index.test.ts +++ b/src/controllers/queries/__tests__/index.test.ts @@ -1,8 +1,9 @@ import { expect } from 'chai' import { describe, it } from 'mocha' import sinon from 'sinon' + import { QueriesController } from '../index.js' -import { toHTMLString, withQueriesMocks } from './helpers.js' +import { mockIds, toHTMLString, withQueriesMocks } from './helpers.js' describe('QueriesController', () => { afterEach(() => { @@ -157,7 +158,6 @@ describe('QueriesController', () => { companyId: '2345789', action: 'success', totalScope3CarbonEmissions: '25', - partialResponse: 1, }) .then(toHTMLString) @@ -189,23 +189,12 @@ describe('QueriesController', () => { companyId: '2345789', action: 'success', totalScope3CarbonEmissions: '25', - partialResponse: 1, }) .then(toHTMLString) expect(result).to.equal('scope3_error_scope3') }) }) - describe('viewing query responses', () => { - it('should call db as expected', async () => { - const { args, dbMock } = withQueriesMocks() - const controller = new QueriesController(...args) - const spy = dbMock.get - await controller.scope3CarbonConsumptionViewResponse('SomeId').then(toHTMLString) - const search = 'SomeId' - expect(spy.firstCall.calledWith('query', { id: search })).to.equal(true) - }) - }) it('should call page with stage error if rpc succeeds with error', async () => { const { args, cloudagentMock } = withQueriesMocks() cloudagentMock.submitDrpcRequest = sinon.stub().resolves({ @@ -219,10 +208,83 @@ describe('QueriesController', () => { companyId: '2345789', action: 'success', totalScope3CarbonEmissions: '25', - partialResponse: 1, }) .then(toHTMLString) expect(result).to.equal('scope3_error_scope3') }) + describe('viewing query responses', () => { + it('should call db as expected', async () => { + const { args, dbMock } = withQueriesMocks() + const controller = new QueriesController(...args) + const spy = dbMock.get + await controller.scope3CarbonConsumptionViewResponse('SomeId').then(toHTMLString) + const search = 'SomeId' + expect(spy.firstCall.calledWith('query', { id: search })).to.equal(true) + }) + }) + describe('Partial Query', () => { + const { args, dbMock } = withQueriesMocks() + let result: string + + before(async () => { + sinon.restore() + + const controller = new QueriesController(...args) + result = await controller + .scope3CO2Partial( + mockIds.queryId, // url param + 'on' // partialSelect query string param + ) + .then(toHTMLString) + }) + + afterEach(() => sinon.restore()) + + describe('if query string param [partialQuery] not provided', () => { + it('renders a regular form template', async () => { + const controller = new QueriesController(...args) + result = await controller + .scope3CO2Partial( + mockIds.queryId // url param + ) + .then(toHTMLString) + + expect(result).to.be.equal('queriesResponse_template') + }) + }) + + it('returns the correct HTML template', () => { + expect(result).to.contain('queriesResponse_template') + }) + + it('retrieves connections and query details from a database', async () => { + expect(dbMock.get.args).to.deep.equal([ + ['query', { id: '00000000-0000-0000-0000-d8ae0805059e' }], + ['connection', { id: 'cccccccc-0001-0000-0000-d8ae0805059e' }], + ['connection', { status: 'verified_both' }], + ]) + }) + + it('pulls connections and returns along with partial = true', async () => { + const formatted = JSON.parse(result.replace('queriesResponse_template-', '')) + + expect(result).to.contain('queriesResponse_template-') + expect(formatted.partial).to.be.equal(true) + expect(formatted.connections).to.deep.equal([ + { + agent_connection_id: 'aaaaaaaa-0000-0000-0000-d8ae0805059e', + company_name: 'PARTIAL_QUERY', + id: 'cccccccc-0000-0000-0000-d8ae0805059e', + status: 'verified_both', + }, + { + agent_connection_id: 'aaaaaaaa-0000-0000-0000-d8ae0805059e', + company_name: 'VERIFIED_THEM', + id: 'cccccccc-0000-0000-0000-d8ae0805059e', + status: 'verified_them', + }, + ]) + }) + }) }) diff --git a/src/controllers/queries/index.ts b/src/controllers/queries/index.ts index 00f0f392..f3a4dd55 100644 --- a/src/controllers/queries/index.ts +++ b/src/controllers/queries/index.ts @@ -10,20 +10,10 @@ import { type UUID } from '../../models/strings.js' import VeritableCloudagent, { DrpcResponse } from '../../models/veritableCloudagent.js' import QueriesTemplates from '../../views/queries/queries.js' import QueryListTemplates from '../../views/queries/queriesList.js' -import Scope3CarbonConsumptionResponseTemplates from '../../views/queries/queryResponses/respondToScope3.js' -import Scope3CarbonConsumptionViewResponseTemplates from '../../views/queries/queryResponses/viewResponseToScope3.js' -import Scope3CarbonConsumptionTemplates from '../../views/queryTypes/scope3.js' +import Scope3CarbonConsumptionTemplates from '../../views/queries/requestCo2scope3.js' +import Scope3CarbonConsumptionResponseTemplates from '../../views/queries/responseCo2scope3.js' import { HTML, HTMLController } from '../HTMLController.js' -type QueryStatus = 'resolved' | 'pending_your_input' | 'pending_their_input' -interface Query { - id: UUID - company_name: string - query_type: string - updated_at: Date - status: QueryStatus -} - @singleton() @injectable() @Security('oauth2') @@ -33,7 +23,6 @@ export class QueriesController extends HTMLController { constructor( private scope3CarbonConsumptionTemplates: Scope3CarbonConsumptionTemplates, private scope3CarbonConsumptionResponseTemplates: Scope3CarbonConsumptionResponseTemplates, - private scope3CarbonConsumptionViewResponseTemplates: Scope3CarbonConsumptionViewResponseTemplates, private queriesTemplates: QueriesTemplates, private queryManagementTemplates: QueryListTemplates, private cloudagent: VeritableCloudagent, @@ -224,13 +213,77 @@ export class QueriesController extends HTMLController { } return this.html( - this.scope3CarbonConsumptionResponseTemplates.newScope3CarbonConsumptionResponseFormPage( - 'form', - connection, + this.scope3CarbonConsumptionResponseTemplates.newScope3CarbonConsumptionResponseFormPage({ + formStage: 'form', + company: connection, queryId, - query.details['quantity'], - query.details['productId'] - ) + quantity: query.details['quantity'], + productId: query.details['productId'], + }) + ) + } + + /** + * @param param.queryId:UUID - query uuid identifier + * @param param.companyId:UUID - connection uuid identifier + * @param query.partialQuery:'on' - either render partial. Checkbox state for checked it will + * add query string to the URL using the name of the input: (http://localhost:3000/form?=on) + * + * @returns a table of connections for partial query + */ + @SuccessResponse(200) + @Get('/{queryId}/partial/{companyId}') + public async scope3CO2Partial(@Path() queryId: UUID, @Query() partialQuery?: 'on'): Promise { + this.logger.debug('partial query response requested %s', queryId) + const [query]: QueryRow[] = await this.db.get('query', { id: queryId }) + if (!query) throw new NotFoundError('query not found') + + const [company]: ConnectionRow[] = await this.db.get('connection', { id: query.connection_id }) + if (!company) throw new NotFoundError('company connection not found') + + this.logger.debug('query and connection - are found %j', { company, query }) + const connections: ConnectionRow[] = await this.db.get('connection', { status: 'verified_both' }) + + // due to very long names, re-assigning to a shorter variable (render) + const render = this.scope3CarbonConsumptionResponseTemplates.newScope3CarbonConsumptionResponseFormPage + this.logger.info('rendering partial query %j', query.details) + + return this.html( + render({ + ...query.details, + company, + queryId, + partial: partialQuery === 'on' ? true : false, + connections: connections.filter(({ id }: ConnectionRow) => id !== query.connection_id), + formStage: 'form', + }) + ) + } + + /** + * @param param.connectionId:UUID + * @param query.partialSelect:'on' - if it's selected by checkbox, then it would add 'on' as a the query string + * to the URL: (http://localhost:3000/form?=on) + * + * @returns - a tabe row for partial query + */ + @SuccessResponse(200) + @Get('/partial-select/{connectionId}/') + public async partialSelect(@Path() connectionId: UUID, @Query() partialSelect?: 'on'): Promise { + this.logger.debug('partial select %s', connectionId) + const [company]: ConnectionRow[] = await this.db.get('connection', { id: connectionId }) + if (!company) throw new NotFoundError('connection not found') + + this.logger.debug('selected: %j and will be returning an updated table row', { company }) + const checked: boolean = partialSelect === 'on' || false + + return this.html( + this.scope3CarbonConsumptionResponseTemplates.tableRow({ + id: connectionId, + checked, + company_name: company.company_name, + company_number: company.company_number, + }) ) } @@ -246,32 +299,22 @@ export class QueriesController extends HTMLController { companyId: UUID action: 'success' totalScope3CarbonEmissions: string - partialResponse?: number } ): Promise { this.logger.debug('query page requested') - const [connection]: ConnectionRow[] = await this.db.get( - 'connection', - { id: body.companyId, status: 'verified_both' }, - [['updated_at', 'desc']] - ) + const [connection] = await this.db.get('connection', { id: body.companyId, status: 'verified_both' }, [ + ['updated_at', 'desc'], + ]) if (!connection) { throw new InvalidInputError(`Invalid connection ${body.companyId}`) } if (!connection.agent_connection_id || connection.status !== 'verified_both') { throw new InvalidInputError(`Cannot query unverified connection`) } - - const [queryRow]: QueryRow[] = await this.db.get('query', { id: queryId }) - if (!queryRow) { - throw new NotFoundError(`There has been an issue retrieving the query.`) - } else if (!queryRow.response_id) { - throw new InvalidInputError('Missing queryId to respond to.') - } else if (queryRow.connection_id !== connection.id) { - throw new NotFoundError(`Missing queryId to respond to.`) - } else if (queryRow.status === 'errored') { - throw new InvalidInputError('Cannot process query with status - "errored"') + const [queryRow] = await this.db.get('query', { id: queryId }) + if (!queryRow.response_id) { + throw new InvalidInputError(`Missing queryId to respond to.`) } const query = { @@ -319,7 +362,10 @@ export class QueriesController extends HTMLController { } return this.html( - this.scope3CarbonConsumptionResponseTemplates.newScope3CarbonConsumptionResponseFormPage('success', connection) + this.scope3CarbonConsumptionResponseTemplates.newScope3CarbonConsumptionResponseFormPage({ + formStage: 'success', + company: connection, + }) ) } @@ -330,7 +376,7 @@ export class QueriesController extends HTMLController { @Get('/scope-3-carbon-consumption/{queryId}/view-response') public async scope3CarbonConsumptionViewResponse(@Path() queryId: UUID): Promise { this.logger.debug('requested to view response to a query %j', { queryId }) - const [query] = await this.db.get('query', { id: queryId }) + const [query]: QueryRow[] = await this.db.get('query', { id: queryId }) if (!query) { throw new NotFoundError(`There has been an issue retrieving the query.`) @@ -345,7 +391,7 @@ export class QueriesController extends HTMLController { } return this.html( - this.scope3CarbonConsumptionViewResponseTemplates.scope3CarbonConsumptionViewResponsePage({ + this.scope3CarbonConsumptionResponseTemplates.view({ company_name: connection.company_name, quantity: query.details['quantity'], productId: query.details['productId'], diff --git a/src/views/common.tsx b/src/views/common.tsx index 29aaff63..d5b6756f 100644 --- a/src/views/common.tsx +++ b/src/views/common.tsx @@ -5,9 +5,9 @@ type HeaderLink = { name: string; url: string } type PageProps = { title: string - heading: string activePage: 'categories' | 'queries' | 'connections' | 'certifications' | 'settings' headerLinks: HeaderLink[] + heading?: string stylesheets?: string[] } @@ -147,7 +147,7 @@ export const Page = (props: PropsWithChildren): JSX.Element => (
- + {props.heading && }
{props.children}
diff --git a/src/views/newConnection/__tests__/fromInvite.test.ts.snap b/src/views/newConnection/__tests__/fromInvite.test.ts.snap index 5cf88348..90001b8f 100644 --- a/src/views/newConnection/__tests__/fromInvite.test.ts.snap +++ b/src/views/newConnection/__tests__/fromInvite.test.ts.snap @@ -7,13 +7,3 @@ exports[`FromInviteTemplates show form should render a web page with the a form exports[`FromInviteTemplates show form should render a web page with the a form in an error state 1`] = `"Veritable - New Connection
Invite New Connection
Step 1 of 3
This is a test error message
Cancel
"`; exports[`FromInviteTemplates show form should render form with a error message and invalid response 1`] = `"
Step 1 of 3
This is a message
Cancel
"`; - -exports[`NewInviteTemplates show form should render a web page with the a form in an empty state 1`] = `"Veritable - New Connection
Invite New Connection
*\\">
Step 1 of 2

Registered Office AddressDIGITAL CATAPULT, Level 9, 101 Euston Road, London, NW1 2RA

Company Statusactive

\\"Description
Cancel
"`; - -exports[`NewInviteTemplates show form should render a web page with the a form in an empty state 2`] = `"Veritable - New Connection
Invite New Connection
*\\">
Step 1 of 2
This is a message
Cancel
"`; - -exports[`NewInviteTemplates show form should render a web page with the a form in an error state 1`] = `"Veritable - New Connection
Invite New Connection
*\\">
Step 1 of 2
This is a test error message
Cancel
"`; - -exports[`NewInviteTemplates show form should render form with a error message and invalid response 1`] = `"
*\\">
Step 1 of 2
This is a message
Cancel
"`; - -exports[`NewInviteTemplates show form should render form with a valid response 1`] = `"
*\\">
Step 2 of 2

Your connection has been established, but still needs to be verified. You should receive a verification letter at your registered business with instructions on how to do this. A reciprocal verification request has been sent in the post on your behalf to the address on the right to verify their identity

Registered Office AddressDIGITAL CATAPULT, Level 9, 101 Euston Road, London, NW1 2RA

Company Statusactive

\\"Description
"`; diff --git a/src/views/newConnection/__tests__/pinSubmission.test.ts.snap b/src/views/newConnection/__tests__/pinSubmission.test.ts.snap index 2cc74ffd..a1fcb18a 100644 --- a/src/views/newConnection/__tests__/pinSubmission.test.ts.snap +++ b/src/views/newConnection/__tests__/pinSubmission.test.ts.snap @@ -1,13 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`NewInviteTemplates show form should render a web page with the a form in an empty state 1`] = `"
*\\">
Step 2 of 2

PIN Code 123456 has been submitted for 00000001 company ID. Please wait for the verification code to be confirmed by viewing the verification. status.

[submitPinCode]: PIN code has been sucessfully submitted and will need to be verified by the issuer now.
"`; - -exports[`NewInviteTemplates show form should render form with a error message and invalid response 1`] = `"Veritable - New Connection

New Connection - PIN Verification

PIN Code submission
*\\">
Step 1 of 2

Please enter the verification code from the physical letter

"`; - -exports[`NewInviteTemplates show form should render form with a valid response 1`] = `"Veritable - New Connection

New Connection - PIN Verification

PIN Code submission
*\\">
Step 1 of 2

Please enter the verification code from the physical letter

"`; - -exports[`NewInviteTemplates show form should render form with a valid response 2`] = `"Veritable - New Connection

New Connection - PIN Verification

PIN Code submission
*\\">
Step 1 of 2

Please enter the verification code from the physical letter

"`; - exports[`PinSubmissionTemplates show form should render form as a continuation of from invite 1`] = `"Veritable - New Connection

New Connection - PIN Verification

PIN Code submission
Step 2 of 3

Please enter the verification code from the physical letter

"`; exports[`PinSubmissionTemplates show form should render form as a stand alone flow 1`] = `"Veritable - New Connection

New Connection - PIN Verification

PIN Code submission
Step 1 of 2

Please enter the verification code from the physical letter

"`; @@ -16,8 +8,6 @@ exports[`PinSubmissionTemplates show form should render form with PIN 1`] = `"Veritable - New Connection

New Connection - PIN Verification

PIN Code submission
Step 2 of 3

Please enter the verification code from the physical letter

There has been an issue, remaining tries: 5

"`; -exports[`PinSubmissionTemplates show form should render form with error message on pin 2`] = `"
Step 3 of 3

You have run out of PIN attempts forCompanyName company ID. Please contact the company to request a pin resend.

there has been an error submitting this pin
"`; - exports[`PinSubmissionTemplates show sucess screen should render success form with error message on pin tries 1`] = `"
Step 3 of 3

You have run out of PIN attempts for CompanyName company ID. The limit on pin tries has been exceeded.

"`; exports[`PinSubmissionTemplates show sucess screen should render sucess form 1`] = `"
Step 3 of 3

PIN Code has been submitted for CompanyName company ID. Please wait for the verification code to be confirmed by viewing theverification status.

"`; diff --git a/src/views/queries/__tests__/partial-query.test.ts b/src/views/queries/__tests__/partial-query.test.ts new file mode 100644 index 00000000..03467d8c --- /dev/null +++ b/src/views/queries/__tests__/partial-query.test.ts @@ -0,0 +1,70 @@ +import { expect } from 'chai' +import { describe } from 'mocha' +import { mockIds } from '../../../controllers/queries/__tests__/helpers.js' +import { ConnectionRow } from '../../../models/db/types.js' +import Templates from '../responseCo2scope3.js' + +const templates = new Templates() +const sampleDate = new Date(Date.UTC(2024, 6, 4)) +const connectionsExample: ConnectionRow[] = [{}, {}, {}, {}].map((_, i) => ({ + id: `${mockIds.connectionId.substr(0, mockIds.connectionId.length - 1)}${i}`, + company_name: 'I own you', + company_number: '3546783', + status: 'verified_both', + agent_connection_id: '233495875757', + pin_attempt_count: 0, + pin_tries_remaining_count: null, + created_at: sampleDate, + updated_at: sampleDate, +})) + +describe('Partial Query', () => { + describe('if partial is set to false', () => { + it('does not render connections list and leaves quantity input enabled', async () => { + const rendered = await templates.newScope3CarbonConsumptionResponseFormPage({ + formStage: 'form', + company: { + company_name: 'VER123', + company_number: '3456789', + status: 'verified_both', + id: mockIds.companyId, + agent_connection_id: mockIds.agentConnectionId, + pin_tries_remaining_count: null, + pin_attempt_count: 0, + created_at: new Date(), + updated_at: new Date(), + }, + queryId: mockIds.queryId, + quantity: 2, + partial: false, + connections: connectionsExample, + productId: 'product-id-test', + }) + + expect(rendered).to.matchSnapshot() + }) + }) + + it('renders partial query table with connections that are verified', async () => { + const rendered = await templates.newScope3CarbonConsumptionResponseFormPage({ + formStage: 'form', + company: { + company_name: 'VER123', + company_number: '3456789', + status: 'verified_both', + id: mockIds.companyId, + agent_connection_id: mockIds.agentConnectionId, + pin_tries_remaining_count: null, + pin_attempt_count: 0, + created_at: new Date(), + updated_at: new Date(), + }, + queryId: 'query-id-test', + quantity: 2, + partial: true, + connections: connectionsExample, + productId: 'product-id-test', + }) + expect(rendered).to.matchSnapshot() + }) +}) diff --git a/src/views/queries/__tests__/partial-query.test.ts.snap b/src/views/queries/__tests__/partial-query.test.ts.snap new file mode 100644 index 00000000..09fd653e --- /dev/null +++ b/src/views/queries/__tests__/partial-query.test.ts.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Partial Query if partial is set to false does not render connections list and leaves quantity input enabled 1`] = `"Veritable - Select Company

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Provide the total scope 3 carbon consumption for the specified products / component.

If you do not have all the required information, please forward this query to your suppliers, to aggregate their responses before submitting the final total.

What are the total Scope 3 carbon emissions for the product/component below?

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID: product-id-test
Quantity: 2

*If partial response checkbox is ticked, you must share this query with another supplier in your network, where your responses will be aggregated.


"`; + +exports[`Partial Query renders partial query table with connections that are verified 1`] = `"Veritable - Select Company

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Provide the total scope 3 carbon consumption for the specified products / component.

If you do not have all the required information, please forward this query to your suppliers, to aggregate their responses before submitting the final total.

What are the total Scope 3 carbon emissions for the product/component below?

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID: product-id-test
Quantity: 2

*If partial response checkbox is ticked, you must share this query with another supplier in your network, where your responses will be aggregated.

SelectCompany NameProduct IDQuantity
I own you
I own you
I own you
I own you

"`; diff --git a/src/views/queries/__tests__/queriesList.test.ts.snap b/src/views/queries/__tests__/queriesList.test.ts.snap index 90207c0f..adf9260e 100644 --- a/src/views/queries/__tests__/queriesList.test.ts.snap +++ b/src/views/queries/__tests__/queriesList.test.ts.snap @@ -2,7 +2,7 @@ exports[`ConnectionTemplates listPage should escape html in name 1`] = `"Veritable - Queries

Query Management

Query Management
Query Request
Query Management
Company NameQuery TypeDirectionRequested deadlineVerification StatusActions
<div>I own you</div>Type A

Received

Resolved
View Response
"`; -exports[`ConnectionTemplates listPage should render multiple with each status 1`] = `"Veritable - Queries

Query Management

Query Management
Query Request
Query Management
Company NameQuery TypeDirectionRequested deadlineVerification StatusActions
Company AType A

Received

Resolved
View Response
Company BType A

Received

Resolved
View Response
Company CType A

Received

Resolved
View Response
Company DType A

Received

Resolved
View Response
Company EType A

Received

Resolved
View Response
Company FType A

Received

Resolved
View Response
Company GType A

not sure

Errored
no action available
"`; +exports[`ConnectionTemplates listPage should render multiple with each status 1`] = `"Veritable - Queries

Query Management

Query Management
Query Request
Query Management
Company NameQuery TypeDirectionRequested deadlineVerification StatusActions
Company AType A

Received

Resolved
View Response
Company BType A

Received

Resolved
View Response
Company CType A

Received

Resolved
View Response
Company DType A

Received

Resolved
View Response
Company EType A

Received

Resolved
View Response
Company FType A

Received

Resolved
View Response
Company GType A

unknown

Errored
no action available
"`; exports[`ConnectionTemplates listPage should render with no connections 1`] = `"Veritable - Queries

Query Management

Query Management
Query Request
Query Management
Company NameQuery TypeDirectionRequested deadlineVerification StatusActions
No Queries for that search. Try again or add a new query
"`; diff --git a/src/views/queries/__tests__/queryResponsesTests/scope3.test.ts b/src/views/queries/__tests__/queryResponsesTests/scope3.test.ts deleted file mode 100644 index 4075d2c2..00000000 --- a/src/views/queries/__tests__/queryResponsesTests/scope3.test.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { expect } from 'chai' -import { describe } from 'mocha' -import Scope3CarbonConsumptionResponseTemplates from '../../queryResponses/respondToScope3.js' -import Scope3CarbonConsumptionViewResponseTemplates from '../../queryResponses/viewResponseToScope3.js' - -describe('QueryResponseTemplates', () => { - describe('newScope3CarbonConsumptionResponseFormPage Tests', () => { - it('should render with test data', async () => { - const templates = new Scope3CarbonConsumptionResponseTemplates() - const rendered = await templates.newScope3CarbonConsumptionResponseFormPage( - 'form', - { - company_name: 'VER123', - company_number: '3456789', - status: 'verified_both', - id: '11', - agent_connection_id: 'agentId', - pin_tries_remaining_count: null, - pin_attempt_count: 0, - created_at: new Date(), - updated_at: new Date(), - }, - 'xyz1234', - 2, - 'cg34jdt' - ) - expect(rendered).to.matchSnapshot() - }) - it('should escape html in name', async () => { - const templates = new Scope3CarbonConsumptionResponseTemplates() - const rendered = await templates.newScope3CarbonConsumptionResponseFormPage( - 'form', - { - company_name: '
I own you
', - company_number: '3456789', - status: 'verified_both', - id: '11', - agent_connection_id: 'agentId', - pin_tries_remaining_count: null, - pin_attempt_count: 0, - created_at: new Date(), - updated_at: new Date(), - }, - 'xyz1234', - 2, - 'cg34jdt' - ) - expect(rendered).to.matchSnapshot() - }) - }) - describe('newQuerySuccess Tests', () => { - it('should render sucess query response page', async () => { - const templates = new Scope3CarbonConsumptionResponseTemplates() - const rendered = await templates.newScope3CarbonConsumptionResponseFormPage('success', { - company_name: 'VER123', - company_number: '3456789', - status: 'verified_both', - id: '11', - agent_connection_id: 'agentId', - pin_tries_remaining_count: null, - pin_attempt_count: 0, - created_at: new Date(), - updated_at: new Date(), - }) - expect(rendered).to.matchSnapshot() - }) - - it('should escape html in name', async () => { - const templates = new Scope3CarbonConsumptionResponseTemplates() - const rendered = await templates.newScope3CarbonConsumptionResponseFormPage('success', { - company_name: '
I own you
', - company_number: '3456789', - status: 'verified_both', - id: '11', - agent_connection_id: 'agentId', - pin_tries_remaining_count: null, - pin_attempt_count: 0, - created_at: new Date(), - updated_at: new Date(), - }) - expect(rendered).to.matchSnapshot() - }) - }) - describe('viewing query response Tests', () => { - const sampleDate = new Date(Date.UTC(2024, 6, 4)) - it('should render resolved query to view response', async () => { - const templates = new Scope3CarbonConsumptionViewResponseTemplates() - const rendered = await templates.scope3CarbonConsumptionViewResponsePage({ - id: '11', - company_name: 'VER123', - query_type: 'Scope 3 query', - updated_at: sampleDate, - status: 'resolved', - role: 'requester', - quantity: '430', - productId: 'jkl333', - emissions: '5678', - }) - expect(rendered).to.matchSnapshot() - }) - it('shouldexcape html resolved query to view response', async () => { - const templates = new Scope3CarbonConsumptionViewResponseTemplates() - const rendered = await templates.scope3CarbonConsumptionViewResponsePage({ - id: '11', - company_name: '
VER123
', - query_type: 'Scope 3 query', - updated_at: sampleDate, - status: 'resolved', - role: 'requester', - quantity: '
430
', - productId: 'jkl333', - emissions: '5678', - }) - expect(rendered).to.matchSnapshot() - }) - }) -}) diff --git a/src/views/queryTypes/__tests__/scope3.test.ts b/src/views/queries/__tests__/requestCo2scope3.test.ts similarity index 97% rename from src/views/queryTypes/__tests__/scope3.test.ts rename to src/views/queries/__tests__/requestCo2scope3.test.ts index 77f1439b..d321f36b 100644 --- a/src/views/queryTypes/__tests__/scope3.test.ts +++ b/src/views/queries/__tests__/requestCo2scope3.test.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import { describe, test } from 'mocha' import { ConnectionRow } from '../../../models/db/types.js' -import Scope3CarbonConsumptionTemplates from '../scope3.js' +import Scope3CarbonConsumptionTemplates from '../requestCo2scope3.js' describe('Scope3CarbonConsumptionTemplates', () => { describe('newScope3CarbonConsumptionFormPage', () => { diff --git a/src/views/queryTypes/__tests__/scope3.test.ts.snap b/src/views/queries/__tests__/requestCo2scope3.test.ts.snap similarity index 85% rename from src/views/queryTypes/__tests__/scope3.test.ts.snap rename to src/views/queries/__tests__/requestCo2scope3.test.ts.snap index 303d3d28..eb078671 100644 --- a/src/views/queryTypes/__tests__/scope3.test.ts.snap +++ b/src/views/queries/__tests__/requestCo2scope3.test.ts.snap @@ -6,8 +6,8 @@ exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage com exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage error 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

An unknown error occurred whilst submitting your query to: company1.

Please try again later.

Back to Queries
"`; -exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage form with product and quantity inputs 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Creates a query for calculating the total scope 3 carbon consumption for a given product or component.

Choose the product that you want to apply the query “What is your scope 1, 2, 3 carbon consumption?” to.

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID

Quantity of product

"`; +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage form with product and quantity inputs 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Creates a query for calculating the total scope 3 carbon consumption for a given product or component.

Choose the product that you want to apply the query “What is your scope 1, 2, 3 carbon consumption?” to.

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID

Quantity of product

"`; -exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage form without product and quantity inputs 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Creates a query for calculating the total scope 3 carbon consumption for a given product or component.

Choose the product that you want to apply the query “What is your scope 1, 2, 3 carbon consumption?” to.

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID

Quantity of product

"`; +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage form without product and quantity inputs 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Creates a query for calculating the total scope 3 carbon consumption for a given product or component.

Choose the product that you want to apply the query “What is your scope 1, 2, 3 carbon consumption?” to.

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID

Quantity of product

"`; exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage success 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Your query request has been shared with the following supplier: company1.

Please await for responses and check for updates in the query management page.

Back to Queries
"`; diff --git a/src/views/queries/__tests__/responseCo2scope3.test.ts b/src/views/queries/__tests__/responseCo2scope3.test.ts new file mode 100644 index 00000000..d321f36b --- /dev/null +++ b/src/views/queries/__tests__/responseCo2scope3.test.ts @@ -0,0 +1,83 @@ +import { expect } from 'chai' +import { describe, test } from 'mocha' +import { ConnectionRow } from '../../../models/db/types.js' +import Scope3CarbonConsumptionTemplates from '../requestCo2scope3.js' + +describe('Scope3CarbonConsumptionTemplates', () => { + describe('newScope3CarbonConsumptionFormPage', () => { + test('companySelect with companies', async () => { + const templates = new Scope3CarbonConsumptionTemplates() + const rendered = await templates.newScope3CarbonConsumptionFormPage({ + formStage: 'companySelect', + connections: [ + { + id: '1', + company_name: 'company1', + company_number: '111111111', + }, + { + id: '2', + company_name: 'company2', + company_number: '222222222', + }, + ] as ConnectionRow[], + search: 'search-text', + }) + expect(rendered).to.matchSnapshot() + }) + + test('companySelect with no companies', async () => { + const templates = new Scope3CarbonConsumptionTemplates() + const rendered = await templates.newScope3CarbonConsumptionFormPage({ + formStage: 'companySelect', + connections: [], + search: 'search-text', + }) + expect(rendered).to.matchSnapshot() + }) + + test('form without product and quantity inputs', async () => { + const templates = new Scope3CarbonConsumptionTemplates() + const rendered = await templates.newScope3CarbonConsumptionFormPage({ + formStage: 'form', + connectionId: 'connection-id', + }) + expect(rendered).to.matchSnapshot() + }) + + test('form with product and quantity inputs', async () => { + const templates = new Scope3CarbonConsumptionTemplates() + const rendered = await templates.newScope3CarbonConsumptionFormPage({ + formStage: 'form', + connectionId: 'connection-id', + productId: 'product-id', + quantity: 123, + }) + expect(rendered).to.matchSnapshot() + }) + + test('success', async () => { + const templates = new Scope3CarbonConsumptionTemplates() + const rendered = await templates.newScope3CarbonConsumptionFormPage({ + formStage: 'success', + company: { + companyName: 'company1', + companyNumber: '111111111', + }, + }) + expect(rendered).to.matchSnapshot() + }) + + test('error', async () => { + const templates = new Scope3CarbonConsumptionTemplates() + const rendered = await templates.newScope3CarbonConsumptionFormPage({ + formStage: 'error', + company: { + companyName: 'company1', + companyNumber: '111111111', + }, + }) + expect(rendered).to.matchSnapshot() + }) + }) +}) diff --git a/src/views/queries/__tests__/responseCo2scope3.test.ts.snap b/src/views/queries/__tests__/responseCo2scope3.test.ts.snap new file mode 100644 index 00000000..eb078671 --- /dev/null +++ b/src/views/queries/__tests__/responseCo2scope3.test.ts.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage companySelect with companies 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Select a Company to send Query to
*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">
Check CompanyCompany Name
company1
company2
"`; + +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage companySelect with no companies 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Select a Company to send Query to
*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">
Check CompanyCompany Name
No connections for that search. Please establish a connection and try again.
"`; + +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage error 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

An unknown error occurred whilst submitting your query to: company1.

Please try again later.

Back to Queries
"`; + +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage form with product and quantity inputs 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Creates a query for calculating the total scope 3 carbon consumption for a given product or component.

Choose the product that you want to apply the query “What is your scope 1, 2, 3 carbon consumption?” to.

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID

Quantity of product

"`; + +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage form without product and quantity inputs 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Scope 3 Carbon Consumption

Creates a query for calculating the total scope 3 carbon consumption for a given product or component.

Choose the product that you want to apply the query “What is your scope 1, 2, 3 carbon consumption?” to.

*\\" hx-target=\\"main\\" hx-swap=\\"innerHTML\\">

Product ID

Quantity of product

"`; + +exports[`Scope3CarbonConsumptionTemplates newScope3CarbonConsumptionFormPage success 1`] = `"Veritable - New Scope 3 Carbon Consumption Query

Select Company To Send Your Query To

Your query request has been shared with the following supplier: company1.

Please await for responses and check for updates in the query management page.

Back to Queries
"`; diff --git a/src/views/queries/queriesList.tsx b/src/views/queries/queriesList.tsx index b5e0a916..bbaf7243 100644 --- a/src/views/queries/queriesList.tsx +++ b/src/views/queries/queriesList.tsx @@ -1,13 +1,11 @@ import Html from '@kitajs/html' import { singleton } from 'tsyringe' -import { UUID } from '../../models/strings.js' import { LinkButton, Page } from '../common.js' type QueryStatus = 'resolved' | 'pending_your_input' | 'pending_their_input' | 'errored' type QueryRole = 'responder' | 'requester' - -interface Query { - id: UUID +type Query = { + id: string company_name: string query_type: string updated_at: Date @@ -62,7 +60,7 @@ export default class QueryListTemplates { case 'resolved': return

Received

default: - return

not sure

+ return

unknown

} } private buttonText = (status: string | QueryStatus, role: QueryRole) => { diff --git a/src/views/queryTypes/scope3.tsx b/src/views/queries/requestCo2scope3.tsx similarity index 97% rename from src/views/queryTypes/scope3.tsx rename to src/views/queries/requestCo2scope3.tsx index 349b9b46..6f89e6c6 100644 --- a/src/views/queryTypes/scope3.tsx +++ b/src/views/queries/requestCo2scope3.tsx @@ -126,7 +126,7 @@ export default class Scope3CarbonConsumptionTemplates { name="connectionId" value={connection.id} disabled={connection.status !== 'verified_both'} - > + /> {Html.escapeHtml(connection.company_name)} @@ -148,13 +148,13 @@ export default class Scope3CarbonConsumptionTemplates { return (
-
+

Scope 3 Carbon Consumption

Creates a query for calculating the total scope 3 carbon consumption for a given product or component.

-
+

Choose the product that you want to apply the query “What is your scope 1, 2, 3 carbon consumption?” to.

@@ -175,7 +175,7 @@ export default class Scope3CarbonConsumptionTemplates { id="productId-input" name="productId" placeholder="BX20001" - class="query-input-field" + class="input-label" type="text" required value={props.productId} @@ -193,8 +193,8 @@ export default class Scope3CarbonConsumptionTemplates { placeholder="123" pattern="^\d+$" required - value={props.quantity?.toString()} - class="query-input-field" + value={props?.quantity?.toString()} + class="input-label" >

Quantity of product

diff --git a/src/views/queries/responseCo2scope3.tsx b/src/views/queries/responseCo2scope3.tsx new file mode 100644 index 00000000..90425515 --- /dev/null +++ b/src/views/queries/responseCo2scope3.tsx @@ -0,0 +1,322 @@ +import Html from '@kitajs/html' +import { UUID } from 'crypto' +import { singleton } from 'tsyringe' +import { ConnectionRow } from '../../models/db/types.js' +import { FormButton, LinkButton, Page } from '../common.js' + +type QueryStatus = 'resolved' | 'pending_your_input' | 'pending_their_input' | 'errored' + +interface Query { + id: string + company_name: string + query_type: string + updated_at: Date + status: QueryStatus + role: 'responder' | 'requester' + quantity: string + productId: string + emissions: string +} + +export type Scope3FormStage = 'form' | 'success' | 'error' +export interface Scope3FormProps { + formStage: Scope3FormStage + company: ConnectionRow + connections?: ConnectionRow[] + partial?: boolean + queryId?: string | UUID + productId?: string + quantity?: number + emissions?: string +} + +@singleton() +export default class Scope3CarbonConsumptionResponseTemplates { + constructor() {} + + public newScope3CarbonConsumptionResponseFormPage = ({ + formStage, + company, + queryId, + quantity, + productId, + partial, + connections, + }: Scope3FormProps) => { + return ( + +
+
+ +
+
+ ) + } + public scope3 = (props: Scope3FormProps): JSX.Element => { + switch (props.formStage) { + case 'form': + return + case 'success': + return + case 'error': + return + } + } + + public scope3CarbonConsumptionResponseFormPage = ({ + partial = false, + connections = [], + ...props + }: Scope3FormProps) => { + return ( +
+
+

Scope 3 Carbon Consumption

+

+ Provide the total scope 3 carbon consumption for the specified products / component. +

+

+ If you do not have all the required information, please forward this query to your suppliers, to aggregate + their responses before submitting the final total.{' '} +

+
+
+

+ What are the total Scope 3 carbon emissions for the product/component below? +

+
+
+

+ Product ID: {Html.escapeHtml(props.productId)} +
+ Quantity: {props.quantity} +

+ +
+ + +
+
+ + +
+

+ *If partial response checkbox is ticked, you must share this query with another supplier in your + network, where your responses will be aggregated. +

+ {partial && connections && ( +
+ + + {['Select', 'Company Name', 'Product ID', 'Quantity'].map((name: string) => ( + + ))} + + + {connections.length == 0 ? ( + + + + ) : ( + connections.map((connection) => this.tableRow(connection)) + )} + +
{Html.escapeHtml(name)}
No Connections found
+
+ )} +
+ + +
+
+
+ ) + } + + public view = (query: Query): JSX.Element => ( + +
+
+
+
+

Scope 3 Carbon Consumption

+

+ A query for calculationg the total scope 3 carbon consumption for a given product or component. +

+
+
+

Query Information

+
+ + + + + + + + + + + + + + + + + +
Supplier:{Html.escapeHtml(query.company_name)}
ProductID:{Html.escapeHtml(query.productId)}
Quantity:{Html.escapeHtml(query.quantity)}
Query: + Please provide details on the Scope 3 carbon emissions associated with the product. +
+
+

Response Information

+
+
+ + + + + + + + + +
Date Certified: + +
Carbon Emissions:{Html.escapeHtml(query.emissions)}
+
+
+ +
+
+
+
+ ) + + public tableRow = ({ + checked = false, + ...props + }: { + company_number: string + id: string + company_name: string + productId?: string + quantity?: number + checked?: boolean + }): JSX.Element => { + return ( + + + + + + {Html.escapeHtml(props.company_name)} + + + + + + + + ) + } + + private newQuerySuccess = (props: Scope3FormProps): JSX.Element => { + return ( +
+

+ Your query Response has been shared with the following company: {Html.escapeHtml(props.company.company_name)}. +

+

Thank you for answering this query, there is no other action required.

+ +
+ ) + } + + private newQueryError = (props: Scope3FormProps): JSX.Element => { + return ( +
+

+ There has been an error when responding to the querry by following company:{' '} + {Html.escapeHtml(props.company.company_name)}. +

+

Please try again or contact the respective company to resolve this issue.

+ +
+ ) + } +} From c83c1b50e3f2a56f89b77df5f7b081ec307d6757 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:44:18 +0000 Subject: [PATCH 2/5] Update npm - all minor and patch updates --- package-lock.json | 40 ++++++++++++++++++---------------------- package.json | 8 ++++---- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 895de3fc..421e690a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "veritable-ui", - "version": "0.8.51", + "version": "0.8.52", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "veritable-ui", - "version": "0.8.51", + "version": "0.8.52", "license": "Apache-2.0", "dependencies": { "@digicatapult/tsoa-oauth-express": "^0.1.38", @@ -24,10 +24,10 @@ "htmx-ext-json-enc": "^2.0.1", "htmx.org": "^2.0.2", "knex": "^3.1.0", - "nodemailer": "^6.9.14", + "nodemailer": "^6.9.15", "pg": "^8.12.0", - "pino": "^9.3.2", - "pino-http": "^10.2.0", + "pino": "^9.4.0", + "pino-http": "^10.3.0", "swagger-ui-express": "^5.0.1", "tsoa": "^6.4.0", "tsyringe": "^4.8.0", @@ -4940,9 +4940,9 @@ } }, "node_modules/nodemailer": { - "version": "6.9.14", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", - "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", + "integrity": "sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -5264,9 +5264,9 @@ } }, "node_modules/pino": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/pino/-/pino-9.3.2.tgz", - "integrity": "sha512-WtARBjgZ7LNEkrGWxMBN/jvlFiE17LTbBoH0konmBU684Kd0uIiDwBXlcTCW7iJnA6HfIKwUssS/2AC6cDEanw==", + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.4.0.tgz", + "integrity": "sha512-nbkQb5+9YPhQRz/BeQmrWpEknAaqjpAqRK8NwJpmrX/JHu7JuZC5G1CeAwJDJfGes4h+YihC6in3Q2nGb+Y09w==", "license": "MIT", "dependencies": { "atomic-sleep": "^1.0.0", @@ -5364,15 +5364,15 @@ } }, "node_modules/pino-http": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.2.0.tgz", - "integrity": "sha512-am03BxnV3Ckx68OkbH0iZs3indsrH78wncQ6w1w51KroIbvJZNImBKX2X1wjdY8lSyaJ0UrX/dnO2DY3cTeCRw==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-10.3.0.tgz", + "integrity": "sha512-kaHQqt1i5S9LXWmyuw6aPPqYW/TjoDPizPs4PnDW4hSpajz2Uo/oisNliLf7We1xzpiLacdntmw8yaZiEkppQQ==", "license": "MIT", "dependencies": { "get-caller-file": "^2.0.5", "pino": "^9.0.0", "pino-std-serializers": "^7.0.0", - "process-warning": "^3.0.0" + "process-warning": "^4.0.0" } }, "node_modules/pino-pretty": { @@ -5406,12 +5406,6 @@ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, - "node_modules/pino/node_modules/process-warning": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", - "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==", - "license": "MIT" - }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -5625,7 +5619,9 @@ } }, "node_modules/process-warning": { - "version": "3.0.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", + "integrity": "sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==", "license": "MIT" }, "node_modules/proxy-addr": { diff --git a/package.json b/package.json index 990348fd..99c2af21 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "veritable-ui", - "version": "0.8.51", + "version": "0.8.52", "description": "UI for Veritable", "main": "src/index.ts", "type": "module", @@ -46,10 +46,10 @@ "htmx-ext-json-enc": "^2.0.1", "htmx.org": "^2.0.2", "knex": "^3.1.0", - "nodemailer": "^6.9.14", + "nodemailer": "^6.9.15", "pg": "^8.12.0", - "pino": "^9.3.2", - "pino-http": "^10.2.0", + "pino": "^9.4.0", + "pino-http": "^10.3.0", "swagger-ui-express": "^5.0.1", "tsoa": "^6.4.0", "tsyringe": "^4.8.0", From f21bb0d62b38c5c01bfe7852ce990e98a47f5655 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 01:41:28 +0000 Subject: [PATCH 3/5] Update dependency @types/node to ^20.16.4 --- package-lock.json | 12 ++++++------ package.json | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 421e690a..49379472 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "veritable-ui", - "version": "0.8.52", + "version": "0.8.53", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "veritable-ui", - "version": "0.8.52", + "version": "0.8.53", "license": "Apache-2.0", "dependencies": { "@digicatapult/tsoa-oauth-express": "^0.1.38", @@ -43,7 +43,7 @@ "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.21", "@types/mocha": "^10.0.7", - "@types/node": "^20.16.3", + "@types/node": "^20.16.4", "@types/nodemailer": "^6.4.15", "@types/pino-http": "^5.8.4", "@types/sinon": "^17.0.3", @@ -1436,9 +1436,9 @@ } }, "node_modules/@types/node": { - "version": "20.16.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.4.tgz", - "integrity": "sha512-ioyQ1zK9aGEomJ45zz8S8IdzElyxhvP1RVWnPrXDf6wFaUb+kk1tEcVVJkF7RPGM0VWI7cp5U57oCPIn5iN1qg==", + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", "license": "MIT", "dependencies": { "undici-types": "~6.19.2" diff --git a/package.json b/package.json index 99c2af21..8e219b3d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "veritable-ui", - "version": "0.8.52", + "version": "0.8.53", "description": "UI for Veritable", "main": "src/index.ts", "type": "module", @@ -65,7 +65,7 @@ "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.21", "@types/mocha": "^10.0.7", - "@types/node": "^20.16.3", + "@types/node": "^20.16.4", "@types/nodemailer": "^6.4.15", "@types/pino-http": "^5.8.4", "@types/sinon": "^17.0.3", From fdf5cd9dc6b296e3b3bb2d4eae5d2faa848b9527 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 01:47:47 +0000 Subject: [PATCH 4/5] Update npm - all minor and patch updates --- package-lock.json | 14 +++++++------- package.json | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49379472..de715e4c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "veritable-ui", - "version": "0.8.53", + "version": "0.8.54", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "veritable-ui", - "version": "0.8.53", + "version": "0.8.54", "license": "Apache-2.0", "dependencies": { - "@digicatapult/tsoa-oauth-express": "^0.1.38", + "@digicatapult/tsoa-oauth-express": "^0.1.39", "@kitajs/html": "^4.2.1", "@kitajs/ts-html-plugin": "^4.0.2", "@tsoa/runtime": "^6.4.0", @@ -43,7 +43,7 @@ "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.21", "@types/mocha": "^10.0.7", - "@types/node": "^20.16.4", + "@types/node": "^20.16.5", "@types/nodemailer": "^6.4.15", "@types/pino-http": "^5.8.4", "@types/sinon": "^17.0.3", @@ -295,9 +295,9 @@ } }, "node_modules/@digicatapult/tsoa-oauth-express": { - "version": "0.1.38", - "resolved": "https://registry.npmjs.org/@digicatapult/tsoa-oauth-express/-/tsoa-oauth-express-0.1.38.tgz", - "integrity": "sha512-o4liq0IxomGWGj0cB3skZbKmZUZ+mB1yGrhDLA6YH7I8Q2le2JDIol8BkDd6rlqgLveP13yjDk6rOPSLqN8/2Q==", + "version": "0.1.40", + "resolved": "https://registry.npmjs.org/@digicatapult/tsoa-oauth-express/-/tsoa-oauth-express-0.1.40.tgz", + "integrity": "sha512-tCGGBK2OiuQDaNa1w2L1nsTnbd1oKpaiu3srrA5VgIJpFCRRpWaOb/QyYENn/sAxe4cFRk+913UU37wi4vjE+w==", "license": "Apache-2.0", "dependencies": { "jsonwebtoken": "^9.0.2", diff --git a/package.json b/package.json index 8e219b3d..216891f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "veritable-ui", - "version": "0.8.53", + "version": "0.8.54", "description": "UI for Veritable", "main": "src/index.ts", "type": "module", @@ -31,7 +31,7 @@ "xss-scan": "xss-scan" }, "dependencies": { - "@digicatapult/tsoa-oauth-express": "^0.1.38", + "@digicatapult/tsoa-oauth-express": "^0.1.39", "@kitajs/html": "^4.2.1", "@kitajs/ts-html-plugin": "^4.0.2", "@tsoa/runtime": "^6.4.0", @@ -65,7 +65,7 @@ "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.21", "@types/mocha": "^10.0.7", - "@types/node": "^20.16.4", + "@types/node": "^20.16.5", "@types/nodemailer": "^6.4.15", "@types/pino-http": "^5.8.4", "@types/sinon": "^17.0.3", From 2bb4b7be24b0be2aef30c14f8b27b6b5b6192747 Mon Sep 17 00:00:00 2001 From: Paulius Date: Mon, 9 Sep 2024 09:37:13 +0100 Subject: [PATCH 5/5] VR-197: health controller. (#152) * VR-197: health controller. * VR-197: route update. --- package-lock.json | 4 +- package.json | 2 +- .../health/__tests__/health.test.ts | 30 +++++++++++++ src/controllers/health/index.ts | 42 +++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 src/controllers/health/__tests__/health.test.ts create mode 100644 src/controllers/health/index.ts diff --git a/package-lock.json b/package-lock.json index de715e4c..a7d282ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "veritable-ui", - "version": "0.8.54", + "version": "0.8.55", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "veritable-ui", - "version": "0.8.54", + "version": "0.8.55", "license": "Apache-2.0", "dependencies": { "@digicatapult/tsoa-oauth-express": "^0.1.39", diff --git a/package.json b/package.json index 216891f7..4f283db0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "veritable-ui", - "version": "0.8.54", + "version": "0.8.55", "description": "UI for Veritable", "main": "src/index.ts", "type": "module", diff --git a/src/controllers/health/__tests__/health.test.ts b/src/controllers/health/__tests__/health.test.ts new file mode 100644 index 00000000..90c1217c --- /dev/null +++ b/src/controllers/health/__tests__/health.test.ts @@ -0,0 +1,30 @@ +import { expect } from 'chai' +import { describe, it } from 'mocha' +import { pino } from 'pino' +import { Env } from '../../../env.js' +import { ILogger } from '../../../logger.js' + +import { HealthController } from '../index.js' + +const mockLogger: ILogger = pino({ level: 'debug' }) +const mockEnv = { + get: (name: string) => { + if (name === 'PUBLIC_URL') return 'http://www.exampl.com' + if (name === 'API_SWAGGER_TITLE') return 'veritable-ui-unit-test' + return '' + }, +} as Env + +describe('HealthController', () => { + it('returns status ok along with title and public url', async () => { + const controller = new HealthController(mockEnv, mockLogger) + const res = await controller.get('unit-test') + expect(res).to.deep.equal({ + status: 'ok', + details: { + title: 'veritable-ui-unit-test', + url: 'http://www.exampl.com', + }, + }) + }) +}) diff --git a/src/controllers/health/index.ts b/src/controllers/health/index.ts new file mode 100644 index 00000000..b9461920 --- /dev/null +++ b/src/controllers/health/index.ts @@ -0,0 +1,42 @@ +import { Get, Header, Hidden, Route, SuccessResponse } from 'tsoa' +import { inject, injectable, singleton } from 'tsyringe' + +import { Env } from '../../env.js' +import { Logger, type ILogger } from '../../logger.js' + +type Response = { + status: 'ok' + details: { + title: string + url: string + } +} + +@singleton() +@injectable() +@Route('/health') +@Hidden() +export class HealthController { + constructor( + private env: Env, + @inject(Logger) private logger: ILogger + ) { + this.logger = logger.child({ controller: '/health' }) + } + /** + * Retrieves the connection list page + */ + @SuccessResponse(200) + @Get('/') + public get(@Header('User-Agent') agent: string): Response { + this.logger.debug('health requst from %s', agent) + + return { + status: 'ok', + details: { + title: this.env.get('API_SWAGGER_TITLE'), + url: this.env.get('PUBLIC_URL'), + }, + } + } +}