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/.github/workflows/push-prod.yml b/.github/workflows/push-prod.yml index c4ecb1d..684be24 100644 --- a/.github/workflows/push-prod.yml +++ b/.github/workflows/push-prod.yml @@ -27,12 +27,9 @@ 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" - + ref: ${{ inputs.branch_name }} # - uses: actions/cache@v2 # with: # path: ~/.npm @@ -65,7 +62,11 @@ 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/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 +82,11 @@ jobs: run: npm run build:prod working-directory: frontend - # Build backend + # Build backend (requires node 14) + - 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..4edb267 100644 --- a/.github/workflows/push-staging.yml +++ b/.github/workflows/push-staging.yml @@ -27,12 +27,9 @@ 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" - + ref: ${{ inputs.branch_name }} # - uses: actions/cache@v2 # with: # path: ~/.npm @@ -65,7 +62,11 @@ 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/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 +82,11 @@ jobs: run: npm run build:stage working-directory: frontend - # Build backend + # Build backend (requires node 14) + - 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/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..63b3740 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" } } 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 = { 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 } }); 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 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..2313ad3 --- /dev/null +++ b/frontend/src/App/Assessment/Competencies/UngradedCompetency/index.jsx @@ -0,0 +1,114 @@ +import React, { useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; + +import { + selectCompetencyDetails, + updateCompetencyDecision, + isCompetencyEnabled, + updateCompetencyEvaluationNote, +} 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 ( + +
+