diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 87d144711..fbbbed5e4 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -1,22 +1,23 @@ name: Sonar on: - # Trigger analysis when a pull request is merged. - pull_request: + push: branches: - development - - master - + # Trigger analysis when a pull request is merged. + pull_request: + types: [opened, synchronize, reopened] jobs: sonarcloud: - if: github.event.pull_request.merged == true + name: SonarCloud +# if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - with: - # Disabling shallow clone is recommended for improving relevancy of reporting - fetch-depth: 0 - - name: SonarCloud Scan - uses: sonarsource/sonarcloud-github-action@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + - uses: actions/checkout@v3 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@master + env: +# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/package.json b/package.json index 1ec9326ef..ea633e025 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "body-parser": "^1.18.3", "bowser": "^2.9.0", "chalk": "^2.4.2", + "content-disposition": "^0.5.4", "cookie-parser": "^1.4.4", "cookie-signature": "^1.2.1", "cors": "^2.8.5", @@ -173,6 +174,7 @@ "@fsouza/prettierd": "^0.22.2", "@testing-library/react": "^12.1.2", "@types/chai": "^4.3.4", + "@types/content-disposition": "^0.5.8", "@types/cookie-signature": "^1.1.0", "@types/sharp": "^0.31.1", "@types/sinon": "^10.0.13", diff --git a/sonar-project.properties b/sonar-project.properties index faffdc20e..dcd569556 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -1,9 +1,22 @@ -sonar.tests=tests,cypress +sonar.projectName=digital_marketplace +sonar.organization=bcgov-sonarcloud +sonar.projectKey=bcgov_digital_marketplace +sonar.host.url=https://sonarcloud.io/ -sonar.projectName=digital-marketplace +#Path to sources sonar.sources=src/front-end,src/back-end +sonar.exclusions=openshift/templates/app/app-digmkt-deploy-prod.yaml +#sonar.inclusions= + +# Path to tests +sonar.tests=tests +#sonar.test.exclusions= +#sonar.test.inclusions= + +# Source encoding sonar.sourceEncoding=UTF-8 -sonar.organization=bcgov-sonarcloud -sonar.projectKey=bcgov_digital-marketplace -sonar.host.url=https://sonarcloud.io/ +# Exclusions for copy-paste detection +#sonar.cpd.exclusions= + +#sonar.projectVersion=Autoscan diff --git a/src/back-end/lib/db/proposal/sprint-with-us.ts b/src/back-end/lib/db/proposal/sprint-with-us.ts index 02f5362c5..4b7f929f4 100644 --- a/src/back-end/lib/db/proposal/sprint-with-us.ts +++ b/src/back-end/lib/db/proposal/sprint-with-us.ts @@ -1678,19 +1678,38 @@ export const awardSWUProposal = tryDb< for (const id of otherProposalIds) { // Get latest status for proposal and check equal to Evaluated/Awarded - - await connection( - "swuProposalStatuses" - ) - .transacting(trx) - .insert({ - id: generateUuid(), - proposal: id, - createdAt: now, - createdBy: session.user.id, - status: SWUProposalStatus.NotAwarded, - note: "" - }); + const currentStatus = ( + await connection<{ status: SWUProposalStatus }>("swuProposalStatuses") + .whereNotNull("status") + .andWhere("proposal", id) + .select("status") + .orderBy("createdAt", "desc") + .first() + )?.status; + + if ( + currentStatus && + ![ + SWUProposalStatus.Disqualified, + SWUProposalStatus.Draft, + SWUProposalStatus.NotAwarded, + SWUProposalStatus.Submitted, + SWUProposalStatus.Withdrawn + ].includes(currentStatus) + ) { + await connection( + "swuProposalStatuses" + ) + .transacting(trx) + .insert({ + id: generateUuid(), + proposal: id, + createdAt: now, + createdBy: session.user.id, + status: SWUProposalStatus.NotAwarded, + note: "" + }); + } await connection("swuProposals") .where({ id: id }) diff --git a/src/back-end/lib/db/proposal/team-with-us.ts b/src/back-end/lib/db/proposal/team-with-us.ts index b8c18f98b..d474b94bb 100644 --- a/src/back-end/lib/db/proposal/team-with-us.ts +++ b/src/back-end/lib/db/proposal/team-with-us.ts @@ -1342,19 +1342,38 @@ export const awardTWUProposal = tryDb< for (const id of otherProposalIds) { // Get latest status for proposal and check equal to Evaluated/Awarded - - await connection( - "twuProposalStatuses" - ) - .transacting(trx) - .insert({ - id: generateUuid(), - proposal: id, - createdAt: now, - createdBy: session.user.id, - status: TWUProposalStatus.NotAwarded, - note: "" - }); + const currentStatus = ( + await connection<{ status: TWUProposalStatus }>("twuProposalStatuses") + .whereNotNull("status") + .andWhere("proposal", id) + .select("status") + .orderBy("createdAt", "desc") + .first() + )?.status; + + if ( + currentStatus && + ![ + TWUProposalStatus.Disqualified, + TWUProposalStatus.Draft, + TWUProposalStatus.NotAwarded, + TWUProposalStatus.Submitted, + TWUProposalStatus.Withdrawn + ].includes(currentStatus) + ) { + await connection( + "twuProposalStatuses" + ) + .transacting(trx) + .insert({ + id: generateUuid(), + proposal: id, + createdAt: now, + createdBy: session.user.id, + status: TWUProposalStatus.NotAwarded, + note: "" + }); + } await connection("twuProposals") .where({ id: id }) diff --git a/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx b/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx index 9d10d4100..91a61f70d 100644 --- a/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx +++ b/src/back-end/lib/mailer/notifications/proposal/code-with-us.tsx @@ -7,7 +7,7 @@ import { import * as templates from "back-end/lib/mailer/templates"; import { makeSend } from "back-end/lib/mailer/transport"; import React from "react"; -import { CONTACT_EMAIL, EMPTY_STRING } from "shared/config"; +import { EMPTY_STRING } from "shared/config"; import { CWUOpportunity } from "shared/lib/resources/opportunity/code-with-us"; import { CWUProposal, @@ -97,41 +97,6 @@ export async function handleCWUProposalAwarded( } } -export async function handleCWUProposalDisqualified( - connection: db.Connection, - proposalId: Id, - session: AuthenticatedSession -): Promise { - //Notify the disqualified proponent - const proposal = getValidValue( - await db.readOneCWUProposal(connection, proposalId, session), - null - ); - const opportunity = - proposal && - getValidValue( - await db.readOneCWUOpportunity( - connection, - proposal.opportunity.id, - session - ), - null - ); - const disqualifiedProponent = - proposal && - getValidValue( - await db.readOneUser(connection, proposal.createdBy.id), - null - ); - if (proposal && opportunity && disqualifiedProponent) { - await disqualifiedCWUProposalSubmission( - disqualifiedProponent, - opportunity, - proposal - ); - } -} - export async function handleCWUProposalWithdrawn( connection: db.Connection, proposalId: Id, @@ -338,47 +303,6 @@ export async function unsuccessfulCWUProposalSubmissionT( ]; } -export const disqualifiedCWUProposalSubmission = makeSend( - disqualifiedCWUProposalSubmissionT -); - -export async function disqualifiedCWUProposalSubmissionT( - recipient: User, - opportunity: CWUOpportunity, - proposal: CWUProposal | CWUProposalSlim -): Promise { - const title = "Your Code With Us Proposal Has Been Deemed Non-Compliant"; - const description = - "The proposal that you submitted for the following Digital Marketplace opportunity was deemed non-compliant and will not be considered any further:"; - return [ - { - to: recipient.email || [], - subject: title, - html: templates.simple({ - title, - description, - descriptionLists: [makeCWUOpportunityInformation(opportunity)], - body: ( -
-

- We appreciate the time and effort you put into creating your - proposal. Thank you for your interest in this opportunity. -

-

- If you have any questions, please send an email to{" "} - . -

-
- ), - callsToAction: [ - viewCWUOpportunityCallToAction(opportunity), - viewCWUProposalCallToAction(proposal) - ] - }) - } - ]; -} - export const withdrawnCWUProposalSubmissionProposalAuthor = makeSend( withdrawnCWUProposalSubmissionProposalAuthorT ); diff --git a/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx b/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx index abf9646fd..19463dbe6 100644 --- a/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx +++ b/src/back-end/lib/mailer/notifications/proposal/sprint-with-us.tsx @@ -7,7 +7,7 @@ import { import * as templates from "back-end/lib/mailer/templates"; import { makeSend } from "back-end/lib/mailer/transport"; import React from "react"; -import { CONTACT_EMAIL, EMPTY_STRING } from "shared/config"; +import { EMPTY_STRING } from "shared/config"; import { SWUOpportunity } from "shared/lib/resources/opportunity/sprint-with-us"; import { SWUProposal, @@ -105,39 +105,6 @@ export async function handleSWUProposalAwarded( } } -export async function handleSWUProposalDisqualified( - connection: db.Connection, - proposalId: Id, - session: AuthenticatedSession -): Promise { - //Notify the disqualified proponent - const proposal = getValidValue( - await db.readOneSWUProposal(connection, proposalId, session), - null - ); - const opportunity = - proposal && - getValidValue( - await db.readOneSWUOpportunity( - connection, - proposal.opportunity.id, - session - ), - null - ); - const disqualifiedProponent = getValidValue( - await db.readOneSWUProposalAuthor(connection, proposalId), - null - ); - if (proposal && opportunity && disqualifiedProponent) { - await disqualifiedSWUProposalSubmission( - disqualifiedProponent, - opportunity, - proposal - ); - } -} - export async function handleSWUProposalWithdrawn( connection: db.Connection, proposalId: Id, @@ -347,47 +314,6 @@ export async function unsuccessfulSWUProposalSubmissionT( ]; } -export const disqualifiedSWUProposalSubmission = makeSend( - disqualifiedSWUProposalSubmissionT -); - -export async function disqualifiedSWUProposalSubmissionT( - recipient: User, - opportunity: SWUOpportunity, - proposal: SWUProposal | SWUProposalSlim -): Promise { - const title = "Your Sprint With Us Proposal Has Been Deemed Non-Compliant"; - const description = - "The proposal that you submitted for the following Digital Marketplace opportunity was deemed non-compliant and will not be considered any further:"; - return [ - { - to: recipient.email || [], - subject: title, - html: templates.simple({ - title, - description, - descriptionLists: [makeSWUOpportunityInformation(opportunity)], - body: ( -
-

- We appreciate the time and effort you put into creating your - proposal. Thank you for your interest in this opportunity. -

-

- If you have any questions, please send an email to{" "} - . -

-
- ), - callsToAction: [ - viewSWUOpportunityCallToAction(opportunity), - viewSWUProposalCallToAction(proposal) - ] - }) - } - ]; -} - export const withdrawnSWUProposalSubmissionProposalAuthor = makeSend( withdrawnSWUProposalSubmissionProposalAuthorT ); diff --git a/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx b/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx index c5b315d0d..503aad411 100644 --- a/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx +++ b/src/back-end/lib/mailer/notifications/proposal/team-with-us.tsx @@ -7,7 +7,7 @@ import { import * as templates from "back-end/lib/mailer/templates"; import { makeSend } from "back-end/lib/mailer/transport"; import React from "react"; -import { CONTACT_EMAIL, EMPTY_STRING } from "shared/config"; +import { EMPTY_STRING } from "shared/config"; import { TWUOpportunity } from "shared/lib/resources/opportunity/team-with-us"; import { TWUProposal, @@ -105,39 +105,6 @@ export async function handleTWUProposalAwarded( } } -export async function handleTWUProposalDisqualified( - connection: db.Connection, - proposalId: Id, - session: AuthenticatedSession -): Promise { - //Notify the disqualified proponent - const proposal = getValidValue( - await db.readOneTWUProposal(connection, proposalId, session), - null - ); - const opportunity = - proposal && - getValidValue( - await db.readOneTWUOpportunity( - connection, - proposal.opportunity.id, - session - ), - null - ); - const disqualifiedProponent = getValidValue( - await db.readOneTWUProposalAuthor(connection, proposalId), - null - ); - if (proposal && opportunity && disqualifiedProponent) { - await disqualifiedTWUProposalSubmission( - disqualifiedProponent, - opportunity, - proposal - ); - } -} - export async function handleTWUProposalWithdrawn( connection: db.Connection, proposalId: Id, @@ -347,47 +314,6 @@ export async function unsuccessfulTWUProposalSubmissionT( ]; } -export const disqualifiedTWUProposalSubmission = makeSend( - disqualifiedTWUProposalSubmissionT -); - -export async function disqualifiedTWUProposalSubmissionT( - recipient: User, - opportunity: TWUOpportunity, - proposal: TWUProposal | TWUProposalSlim -): Promise { - const title = "Your Team With Us Proposal Has Been Deemed Non-Compliant"; - const description = - "The proposal that you submitted for the following Digital Marketplace opportunity was deemed non-compliant and will not be considered any further:"; - return [ - { - to: recipient.email || [], - subject: title, - html: templates.simple({ - title, - description, - descriptionLists: [makeTWUOpportunityInformation(opportunity)], - body: ( -
-

- We appreciate the time and effort you put into creating your - proposal. Thank you for your interest in this opportunity. -

-

- If you have any questions, please send an email to{" "} - . -

-
- ), - callsToAction: [ - viewTWUOpportunityCallToAction(opportunity), - viewTWUProposalCallToAction(proposal) - ] - }) - } - ]; -} - export const withdrawnTWUProposalSubmissionProposalAuthor = makeSend( withdrawnTWUProposalSubmissionProposalAuthorT ); diff --git a/src/back-end/lib/resources/file.ts b/src/back-end/lib/resources/file.ts index 9284f50f9..81f82e391 100644 --- a/src/back-end/lib/resources/file.ts +++ b/src/back-end/lib/resources/file.ts @@ -1,3 +1,4 @@ +import contentDisposition from "content-disposition"; import * as crud from "back-end/lib/crud"; import * as db from "back-end/lib/db"; import * as permissions from "back-end/lib/permissions"; @@ -89,7 +90,7 @@ const readOne: crud.ReadOne = ( adt("file", { buffer: dbResultBlob.value.blob, contentType: lookup(file.name) || "application/octet-stream", - contentDisposition: `attachment; filename="${file.name}"` + contentDisposition: contentDisposition(file.name) }) ); } diff --git a/src/back-end/lib/resources/proposal/code-with-us.ts b/src/back-end/lib/resources/proposal/code-with-us.ts index 3a5ba7553..6d274902b 100644 --- a/src/back-end/lib/resources/proposal/code-with-us.ts +++ b/src/back-end/lib/resources/proposal/code-with-us.ts @@ -857,12 +857,6 @@ const update: crud.Update< body.value, session ); - // Notify of disqualification - cwuProposalNotifications.handleCWUProposalDisqualified( - connection, - request.params.id, - session - ); break; case "withdraw": dbResult = await db.updateCWUProposalStatus( diff --git a/src/back-end/lib/resources/proposal/sprint-with-us.ts b/src/back-end/lib/resources/proposal/sprint-with-us.ts index bea80b809..bd3c7c15a 100644 --- a/src/back-end/lib/resources/proposal/sprint-with-us.ts +++ b/src/back-end/lib/resources/proposal/sprint-with-us.ts @@ -1616,14 +1616,6 @@ const update: crud.Update< body.value, session ); - // Notify of disqualification - if (isValid(dbResult)) { - swuProposalNotifications.handleSWUProposalDisqualified( - connection, - request.params.id, - request.body.session - ); - } break; case "withdraw": dbResult = await db.updateSWUProposalStatus( diff --git a/src/back-end/lib/resources/proposal/team-with-us.ts b/src/back-end/lib/resources/proposal/team-with-us.ts index 20cef3e86..f875c224b 100644 --- a/src/back-end/lib/resources/proposal/team-with-us.ts +++ b/src/back-end/lib/resources/proposal/team-with-us.ts @@ -1256,14 +1256,6 @@ const update: crud.Update< body.value, session ); - // Notify of disqualification - if (isValid(dbResult)) { - twuProposalNotifications.handleTWUProposalDisqualified( - connection, - request.params.id, - request.body.session - ); - } break; case "withdraw": dbResult = await db.updateTWUProposalStatus( diff --git a/src/back-end/lib/routers/admin/index.tsx b/src/back-end/lib/routers/admin/index.tsx index 67f515bf5..ed82b154d 100644 --- a/src/back-end/lib/routers/admin/index.tsx +++ b/src/back-end/lib/routers/admin/index.tsx @@ -31,7 +31,6 @@ import { import { organizationArchivedT } from "back-end/lib/mailer/notifications/organization"; import { awardedCWUProposalSubmissionT, - disqualifiedCWUProposalSubmissionT, successfulCWUProposalSubmissionT, unsuccessfulCWUProposalSubmissionT, withdrawnCWUProposalSubmissionProposalAuthorT, @@ -39,7 +38,6 @@ import { } from "back-end/lib/mailer/notifications/proposal/code-with-us"; import { awardedSWUProposalSubmissionT, - disqualifiedSWUProposalSubmissionT, successfulSWUProposalSubmissionT, unsuccessfulSWUProposalSubmissionT, withdrawnSWUProposalSubmissionProposalAuthorT, @@ -261,14 +259,6 @@ async function makeEmailNotificationReference(): Promise< )) ] }, - { - title: "CWU Proposal Disqualified", - emails: await disqualifiedCWUProposalSubmissionT( - mocks.vendorUser, - mocks.cwuOpportunity, - mocks.cwuProposal - ) - }, { title: "CWU Proposal Withdrawn", emails: [ @@ -389,14 +379,6 @@ async function makeEmailNotificationReference(): Promise< )) ] }, - { - title: "SWU Proposal Disqualified", - emails: await disqualifiedSWUProposalSubmissionT( - mocks.vendorUser, - mocks.swuOpportunity, - mocks.swuProposal - ) - }, { title: "SWU Proposal Withdrawn", emails: [ diff --git a/yarn.lock b/yarn.lock index 4b751ae98..ce68606cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1946,6 +1946,11 @@ dependencies: "@types/node" "*" +"@types/content-disposition@^0.5.8": + version "0.5.8" + resolved "https://registry.yarnpkg.com/@types/content-disposition/-/content-disposition-0.5.8.tgz#6742a5971f490dc41e59d277eee71361fea0b537" + integrity sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg== + "@types/cookie-parser@^1.4.2": version "1.4.2" resolved "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.2.tgz" @@ -4324,9 +4329,9 @@ content-disposition@0.5.2: resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz" integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= -content-disposition@0.5.4: +content-disposition@0.5.4, content-disposition@^0.5.4: version "0.5.4" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: safe-buffer "5.2.1"