From fa6842175d5872fd9ad1e4fe82bdfc56a8fd6871 Mon Sep 17 00:00:00 2001 From: Brett Maden Date: Mon, 21 Nov 2022 16:56:22 -0500 Subject: [PATCH 01/10] update to node 14.21.1 --- api/Dockerfile | 2 +- api/Dockerfile.compose | 2 +- api/Dockerfile.dev | 2 +- api/package-lock.json | 2 +- api/package.json | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/api/Dockerfile b/api/Dockerfile index c111f5d..ecc6fdb 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -1,4 +1,4 @@ -FROM node:14.19.0-alpine +FROM node:14.21.1-alpine WORKDIR /app diff --git a/api/Dockerfile.compose b/api/Dockerfile.compose index 7429bd3..b09bdc4 100644 --- a/api/Dockerfile.compose +++ b/api/Dockerfile.compose @@ -1,4 +1,4 @@ -FROM node:14.19.0-alpine +FROM node:14.21.1-alpine WORKDIR /opt/node_app/app COPY package.json package-lock.json ./ diff --git a/api/Dockerfile.dev b/api/Dockerfile.dev index 05d77a1..822d779 100644 --- a/api/Dockerfile.dev +++ b/api/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:14.19.0-alpine as builder +FROM node:14.21.1-alpine as builder COPY package.json package-lock.json ./ COPY tsconfig.json ./ diff --git a/api/package-lock.json b/api/package-lock.json index e5fe59a..ed6983a 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -70,7 +70,7 @@ "typescript": "^4.1.3" }, "engines": { - "node": "14.19.1" + "node": "14.21.1" } }, "node_modules/@babel/code-frame": { diff --git a/api/package.json b/api/package.json index adf26c4..f6a522b 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "smeqa-art", - "version": "5.0.0", + "version": "5.0.1", "description": "", "main": "build/server.js", "scripts": { @@ -86,6 +86,6 @@ "winston-daily-rotate-file": "^4.5.0" }, "engines": { - "node": ">14.19.0 <15.0.0" + "node": ">14.21.1 <15.0.0" } } From 2342efbf628ac9ea7e45e3893264941711443105 Mon Sep 17 00:00:00 2001 From: Brett Maden Date: Tue, 22 Nov 2022 15:05:16 -0500 Subject: [PATCH 02/10] narrow node version to match available cf buildpack; update parsing of VCAP_SERVICES --- api/package.json | 2 +- api/src/config/index.ts | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/api/package.json b/api/package.json index f6a522b..63b3740 100644 --- a/api/package.json +++ b/api/package.json @@ -86,6 +86,6 @@ "winston-daily-rotate-file": "^4.5.0" }, "engines": { - "node": ">14.21.1 <15.0.0" + "node": "14.21.1" } } diff --git a/api/src/config/index.ts b/api/src/config/index.ts index 7d36a67..c3aed30 100644 --- a/api/src/config/index.ts +++ b/api/src/config/index.ts @@ -1,7 +1,5 @@ import path from 'path'; import fs from 'fs'; -// const REDSREGEX = /"uri": "(redis:\/\/.*)"/; -const PSQLREGEX = /"uri": "(postgres:\/\/.*)"/; export interface OpenIDConfiguration { issuerDiscover: string; @@ -59,7 +57,7 @@ const env = process.env.APP_ENV?.toString() || 'development'; const dbURI: string = process.env.POSTGRES_URI || - (process.env.VCAP_SERVICES && process.env.VCAP_SERVICES.match(PSQLREGEX)![1]) || + (process.env.VCAP_SERVICES && JSON.parse(process.env.VCAP_SERVICES)["aws-rds"][0].credentials.uri) || 'postgres://docker_pg_user:docker_pg_pw@docker_db:5432/docker_db'; const openIdConfig: OpenIDConfiguration = { From fa60728fc09c4f2588509f73153419cb02ed3148 Mon Sep 17 00:00:00 2001 From: Brett Maden Date: Tue, 22 Nov 2022 15:21:32 -0500 Subject: [PATCH 03/10] add manifest and workflow for demo --- .github/workflows/push-demo.yml | 88 +++++++++++++++++++++++++++++++++ deployDemo.yml | 17 +++++++ 2 files changed, 105 insertions(+) create mode 100644 .github/workflows/push-demo.yml create mode 100644 deployDemo.yml diff --git a/.github/workflows/push-demo.yml b/.github/workflows/push-demo.yml new file mode 100644 index 0000000..6f513fd --- /dev/null +++ b/.github/workflows/push-demo.yml @@ -0,0 +1,88 @@ +name: Deploy Demo + +on: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + inputs: + branch_name: + description: branch to deploy + required: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ inputs.branch_name }} + + - uses: actions/cache@v2 + id: api-npm-cache + with: + path: ./api/node_modules/ + key: ${{ runner.os }}-api-npm-${{ hashFiles('./api/package-lock.json') }} + + - uses: actions/cache@v2 + id: frontend-npm-cache + with: + path: ./frontend/node_modules/ + key: ${{ runner.os }}-frontend-npm-${{ hashFiles('./frontend/package-lock.json') }} + + - uses: actions/cache@v2 + id: frontend-scss-cache + with: + path: ./frontend/src/styles/ + key: ${{ runner.os }}-frontend-scss-${{ hashFiles('./frontend/src/scss/**.scss') }} + + # Notice that the cache for the frontend is actually in the api directory... + - uses: actions/cache@v2 + id: frontend-src-cache + with: + path: ./api/src/client + key: ${{ runner.os }}-frontend-src-${{ hashFiles('./frontend/src/**/*', './frontend/package.json') }} + + # Build frontend + - uses: actions/setup-node@v2 + with: + node-version: "12" + + - name: Install frontend dependencies + if: steps.frontend-npm-cache.outputs.cache-hit != 'true' + run: npm ci + working-directory: frontend + + - name: Build frontend styles + if: steps.frontend-scss-cache.outputs.cache-hit != 'true' + run: npm run build:styles + working-directory: frontend + + - name: Build frontend + if: steps.frontend-src-cache.outputs.cache-hit != 'true' + run: npm run build:stage + working-directory: frontend + + # Build backend + - uses: actions/setup-node@v2 + with: + node-version: "14" + + - name: Install dependencies + if: steps.api-npm-cache.outputs.cache-hit != 'true' + run: npm ci + working-directory: api + + # Because of how the deploy works, do not cache backend + # frontend changes _will not_ make it if that's the case + - name: Build backend + run: npm run build + working-directory: api + + - name: Deploy to cloud.gov + uses: cloud-gov/cg-cli-tools@main + with: + cf_api: https://api.fr.cloud.gov + cf_username: ${{secrets.STAGE_CF_USERNAME}} + cf_password: ${{secrets.STAGE_CF_PASSWORD}} + cf_org: gsa-open-opportunities + cf_space: USDS + cf_manifest: deployDemo.yml diff --git a/deployDemo.yml b/deployDemo.yml new file mode 100644 index 0000000..8c7c855 --- /dev/null +++ b/deployDemo.yml @@ -0,0 +1,17 @@ +version: 1 +applications: + - name: smeqa-demo + instances: 1 + memory: 128MB + env: + NODE_ENV: production + APP_ENV: staging + OPTIMIZE_MEMORY: "true" + TZ: America/New_York + command: node server.js + type: nodejs + path: ./api/build + buildpack: nodejs_buildpack + stack: cflinuxfs3 + services: + - smeqa-demo-db From 76e9444aa176649160a097fc99af3315d583b988 Mon Sep 17 00:00:00 2001 From: Brett Maden Date: Wed, 23 Nov 2022 10:58:33 -0500 Subject: [PATCH 04/10] update workflows: build frontend with node 12 and backend with node 14 --- .github/workflows/push-prod.yml | 20 ++++++++++++-------- .github/workflows/push-staging.yml | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.github/workflows/push-prod.yml b/.github/workflows/push-prod.yml index c4ecb1d..5831485 100644 --- a/.github/workflows/push-prod.yml +++ b/.github/workflows/push-prod.yml @@ -27,12 +27,6 @@ jobs: runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "14" - # - uses: actions/cache@v2 # with: # path: ~/.npm @@ -65,7 +59,12 @@ jobs: path: ./api/src/client key: ${{ runner.os }}-frontend-src-prod-${{ hashFiles('./frontend/src/**/*', './frontend/package.json') }} - # Build frontend + # Build frontend (requires node 12) + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "12" + - name: Install frontend dependencies if: steps.frontend-npm-cache.outputs.cache-hit != 'true' run: npm ci @@ -81,7 +80,12 @@ jobs: run: npm run build:prod working-directory: frontend - # Build backend + # Build backend (requires node 14) + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "14" + - name: Install dependencies if: steps.api-npm-cache.outputs.cache-hit != 'true' run: npm ci diff --git a/.github/workflows/push-staging.yml b/.github/workflows/push-staging.yml index 97c86ad..92a8953 100644 --- a/.github/workflows/push-staging.yml +++ b/.github/workflows/push-staging.yml @@ -27,12 +27,6 @@ jobs: runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: "14" - # - uses: actions/cache@v2 # with: # path: ~/.npm @@ -65,7 +59,12 @@ jobs: path: ./api/src/client key: ${{ runner.os }}-frontend-src-${{ hashFiles('./frontend/src/**/*', './frontend/package.json') }} - # Build frontend + # Build frontend (requires node 12) + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "12" + - name: Install frontend dependencies if: steps.frontend-npm-cache.outputs.cache-hit != 'true' run: npm ci @@ -81,7 +80,12 @@ jobs: run: npm run build:stage working-directory: frontend - # Build backend + # Build backend (requires node 14) + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "14" + - name: Install dependencies if: steps.api-npm-cache.outputs.cache-hit != 'true' run: npm ci From 6dc23eca9e1425d3acbbfd0725537f100e7f5205 Mon Sep 17 00:00:00 2001 From: Kelvin Luu Date: Thu, 15 Dec 2022 04:31:02 -0800 Subject: [PATCH 05/10] Add USDC text --- .../ResumeReviewWithJustifications.jsx | 58 ++++++++++++------- 1 file changed, 36 insertions(+), 22 deletions(-) diff --git a/frontend/src/App/Assessment/ResumeReviewWithJustifications/ResumeReviewWithJustifications.jsx b/frontend/src/App/Assessment/ResumeReviewWithJustifications/ResumeReviewWithJustifications.jsx index 6471aa8..caede95 100644 --- a/frontend/src/App/Assessment/ResumeReviewWithJustifications/ResumeReviewWithJustifications.jsx +++ b/frontend/src/App/Assessment/ResumeReviewWithJustifications/ResumeReviewWithJustifications.jsx @@ -119,38 +119,52 @@ const ResumeReview = ({

Completing a resume review involves the following steps:

  1. - Find the applicant's resume in USA Staffing or through a - batch download on USA Staffing. + Find the applicant's resume in the email sent by the U.S. + Digital Corps team. There may be applicants with similar + names — please ensure you have the right resume.
  2. - Align the browser window alongside the resume so that you - can see both at the same time. + Align the browser window alongside the resume so that you can + see both at the same time.
  3. - Review the applicant's resume for the specialized experience - below. Look for evidence of each pre-defined experience in - their resume. Use the experience they describe in their - resume rather than searching for keywords. + Enter the candidate’s institution, degree program, field of study, + and graduation date for their most recent/current degree, as well + as the graduation month and year for their first degree. This data + will help us assess our recruitment efforts.
  4. - Select “Yes” if the applicant's resume reflects the required - experience. Under “Evaluation Justification” indicate where - in the resume you found support for your rating (i.e., - year(s) experience gained, as well as position title as - needed for clarity). + Review two pages of experience (starting with the first line of + experience) to assess if they meet the minimum qualifications with + experience. Look for evidence of each competency in their resume — + use the experience they describe in their resume rather than searching + for keywords.
  5. - Select “No” if the applicant's resume does not reflect the - required experience. Under “Evaluation Justification,” you - will see text auto-populate (i.e., “Resume reflects - insufficient evidence of …”). Provide additional - justification as needed to help others to retrace your - decision. + Select "Yes" if the applicant’s resume reflects a given competency. + Under "Evaluation Justification," indicate where in the resume you + found support for your rating (i.e., the role and/or year that add + to 52 weeks).
  6. -
  7. Click "Finish Review" to move to the next applicant.
  8. - Keep working through the applicants. Click "Exit Review" at - the top of the page to stop at any time. + Select "No" if the applicant's resume does not reflect a specific + competency. Under "Evaluation Justification," you will see text + auto-populate (i.e., "Does not move forward…"). Provide any + additional information necessary to help others retrace your decision. +
  9. +
  10. + Click "Finish Review" to move to the next applicant. +
  11. +
  12. + Keep working through the applicants. Click "Exit Review" at the + top of the page to stop at any time. Incomplete reviews will not + be saved on exit. +
  13. +
  14. + If you have questions or are unsure about a specific applicant’s + review, click the “Flag applicant for HR” button to contact a + member of the USDC team for assistance. Flagging removes the + applicant from everyone’s queue.
From 054389d51af2b373c6807962834d033781d9df6f Mon Sep 17 00:00:00 2001 From: Kelvin Luu Date: Thu, 15 Dec 2022 04:32:09 -0800 Subject: [PATCH 06/10] Fix bug when there's no applicant --- api/src/services/assessmenthurdle.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/src/services/assessmenthurdle.service.ts b/api/src/services/assessmenthurdle.service.ts index f70c625..4f92989 100644 --- a/api/src/services/assessmenthurdle.service.ts +++ b/api/src/services/assessmenthurdle.service.ts @@ -109,6 +109,7 @@ export default class AssessmentHurdleService { if (!assessmentHurdle) throw new HttpException(404, `${hurdleId} not found`); const applicantsById = assessmentHurdle.Applicants.reduce((memo, a) => { + if (!a) { return memo }; memo[a.id] = a.name!; return memo; }, {} as { [name: string]: string }); @@ -161,6 +162,7 @@ export default class AssessmentHurdleService { attributes: ['name', 'sort_order', 'id'], }) ).reduce((memo, c) => { + if (!c) { return memo;} memo[c.id] = { name: c.name, sort_order: c.sort_order! }; return memo; }, {} as { [competencyId: string]: { name: string; sort_order: number } }); From 7afad1145b762218a72859b1c39cc2719c8fa444 Mon Sep 17 00:00:00 2001 From: Kelvin Luu Date: Thu, 15 Dec 2022 04:33:50 -0800 Subject: [PATCH 07/10] Add competency type for ungraded competency --- .../UngradedCompetency/UngradedCompetency.jsx | 74 +++++++++++ .../Competencies/UngradedCompetency/index.jsx | 115 ++++++++++++++++++ .../ResumeReviewWithJustifications/index.jsx | 12 +- .../src/App/Assessment/assessmentSlice.js | 1 + frontend/src/constants.js | 1 + 5 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 frontend/src/App/Assessment/Competencies/UngradedCompetency/UngradedCompetency.jsx create mode 100644 frontend/src/App/Assessment/Competencies/UngradedCompetency/index.jsx diff --git a/frontend/src/App/Assessment/Competencies/UngradedCompetency/UngradedCompetency.jsx b/frontend/src/App/Assessment/Competencies/UngradedCompetency/UngradedCompetency.jsx new file mode 100644 index 0000000..c27fee7 --- /dev/null +++ b/frontend/src/App/Assessment/Competencies/UngradedCompetency/UngradedCompetency.jsx @@ -0,0 +1,74 @@ +import React from "react"; +import classnames from "classnames"; + +import Accordion from "../../../commonComponents/Accordion"; +import RadioGroup from "../../../commonComponents/RadioGroup"; +import Alert from "../../../commonComponents/Alert"; + +const UngradedCompetency = ({ + name, + definition, + requiredProficiencyDefinition, + children, + updateCompetencyDecisionHandler, + isDisabled, + // doesNotMeetClassToggle, + selectors, + competency_selector_id, + justifications, +}) => { + const definitionAccordion = + definition && definition.length ? ( + +
+
+ ) : null; + let classNames = classnames({ + "grid-row": true, + "grid-gap-2": true, + "smeqa-rr-comp": true, + // "does-not-meet": doesNotMeetClassToggle, + }); + + const horizontal = selectors.length < 3 ? true : false; + return ( +
+
+

{name}

+ +
+ {justifications && + justifications.map((j, i) => ( + + ))} + {definitionAccordion} + + {children} +
+ + +
+ ); +}; + +export default UngradedCompetency; diff --git a/frontend/src/App/Assessment/Competencies/UngradedCompetency/index.jsx b/frontend/src/App/Assessment/Competencies/UngradedCompetency/index.jsx new file mode 100644 index 0000000..d924ad3 --- /dev/null +++ b/frontend/src/App/Assessment/Competencies/UngradedCompetency/index.jsx @@ -0,0 +1,115 @@ +import React, { useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; + +import { + selectCompetencyDetails, + updateCompetencyDecision, + isCompetencyEnabled, + updateCompetencyEvaluationNote, + selectCompetencyResult, +} from "../../assessmentSlice"; +import UngradedCompetency from "./UngradedCompetency"; +// import { COMPETENCY_TYPES } from "../../../../constants"; +import Textarea from "../../../commonComponents/Textarea"; + +const UngradedCompetencyContainer = ({ id }) => { + const dispatch = useDispatch(); + const competencyDetails = useSelector(selectCompetencyDetails(id)); + const { + name, + definition, + requiredProficiencyDefinition, + selectors: allSelectors, + // competencyType, + evaluation, + justifications, + } = competencyDetails; + const { competency_selector_id, evaluation_note } = evaluation; + const [placeholder, setPlaceholder] = useState(""); + + let competencyIsFailing = false; + + const selectors = [...allSelectors] + .sort((sa, sb) => sb.sort_order - sa.sort_order) + .map((s) => { + return { + label: s.display_name, + value: s.id, + defaultText: s.default_text || "", + placeholderText: s.placeholder_text || "", + }; + }); + const defaultTexts = selectors.reduce((memo, s) => { + memo[s.defaultText || ""] = true; + return memo; + }, {}); + console.log(selectors.length) + + if (selectors.length === 1 && !competency_selector_id) { + // const selectorId = selectors[0].value; + const selector = selectors[0]; + setPlaceholder(selector.placeholderText); + dispatch(updateCompetencyDecision({ id, selectorId: selector.value })); + if ( + !evaluation_note || + !evaluation_note.length || + defaultTexts[evaluation_note] + ) { + dispatch( + updateCompetencyEvaluationNote({ id, note: selector.defaultText || "" }) + ); + } + + } + + + const isDisabled = !useSelector(isCompetencyEnabled(id)); + const updateCompetencyDecisionHandler = (e) => { + const selectorId = e.target.value; + + const selector = selectors.find((s) => s.value === selectorId); + setPlaceholder(selector.placeholderText); + + dispatch(updateCompetencyDecision({ id, selectorId: selectorId })); + // If there is no evaluation note: + // Or if the note is a default note. + if ( + !evaluation_note || + !evaluation_note.length || + defaultTexts[evaluation_note] + ) { + dispatch( + updateCompetencyEvaluationNote({ id, note: selector.defaultText || "" }) + ); + } + }; + const updateCompetencyEvaluationNoteHandler = (e) => { + const { value: note } = e.target; + dispatch(updateCompetencyEvaluationNote({ id, note })); + }; + return ( + +
+