From 579da0ae6d667c728533811fc572824e10d28454 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:48:03 +0000 Subject: [PATCH 01/12] Bump ws from 8.5.0 to 8.17.1 Bumps [ws](https://github.com/websockets/ws) from 8.5.0 to 8.17.1. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/8.5.0...8.17.1) --- updated-dependencies: - dependency-name: ws dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 7857d3fcc7..4e06d80867 100644 --- a/package.json +++ b/package.json @@ -358,7 +358,7 @@ "validator": "^13.7.0", "wait-for-expect": "^3.0.2", "winston": "^3.3.3", - "ws": "^8.5.0", + "ws": "^8.17.1", "xml2json": "^0.12.0", "yargs": "^17.3.1", "yayson": "^2.1.0" diff --git a/yarn.lock b/yarn.lock index 855ff743f5..0608a263ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14038,31 +14038,21 @@ write-file-atomic@^3.0.0: signal-exit "^3.0.2" typedarray-to-buffer "^3.1.5" -ws@8.5.0, ws@^8.5.0: +ws@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== -ws@>=8.7.0: - version "8.9.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.9.0.tgz#2a994bb67144be1b53fe2d23c53c028adeb7f45e" - integrity sha512-Ja7nszREasGaYUYCI2k4lCKIRTt+y7XuqVoHR44YpI49TtryyqbqvDMn5eqfW7e6HzTukDRIsXqzVHScqRcafg== +ws@>=8.7.0, ws@^8.14.2, ws@^8.16.0, ws@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== ws@^7.4.6: version "7.5.9" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== -ws@^8.14.2: - version "8.14.2" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.14.2.tgz#6c249a806eb2db7a20d26d51e7709eab7b2e6c7f" - integrity sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g== - -ws@^8.16.0: - version "8.16.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" - integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== - xdg-basedir@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" @@ -14239,4 +14229,4 @@ yayson@^2.1.0: optionalDependencies: lodash "^4.17.21" q "^1.5.1" - underscore "^1.13.1" \ No newline at end of file + underscore "^1.13.1" From 4602ace22a0a9b2f8ee9aca111b7a4035ce33678 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 10:15:21 -0400 Subject: [PATCH 02/12] updates to common package --- packages/common/src/constants.js | 17 +++++++++++++++++ packages/common/src/utils.js | 22 ++++++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/common/src/constants.js b/packages/common/src/constants.js index e763864458..cedb48745b 100644 --- a/packages/common/src/constants.js +++ b/packages/common/src/constants.js @@ -364,3 +364,20 @@ const REOPEN_REASONS = { }; exports.REOPEN_REASONS = REOPEN_REASONS; + +const DISALLOWED_URLS = [{ + url: 'https://eclkc.ohs.acf.hhs.gov/professional-development/individualized-professional-development-ipd-portfolio/course-catalog', + error: 'This link is no longer accepted in this field. Enter iPD courses used during your TTA session in the other field in this section.', +}, { + url: 'https://eclkc.ohs.acf.hhs.gov/professional-development/individualized-professional-development-ipd-portfolio/individualized-professional-development-ipd-portfolio', + error: 'This link is no longer accepted in this field. Enter iPD courses used during your TTA session in the other field in this section.', +}, { + url: 'https://eclkc.ohs.acf.hhs.gov/cas/login', + error: 'This link is no longer accepted in this field. Enter iPD courses used during your TTA session in the other field in this section.', +}]; + +exports.DISALLOWED_URLS = DISALLOWED_URLS; + +const VALID_URL_REGEX = /(?(?http(?:s)?):\/\/(?:(?[a-zA-Z0-9._]+)(?:[:](?[a-zA-Z0-9%._\+~#=]+))?[@])?(?:(?:www\.)?(?[-a-zA-Z0-9%._\+~#=]{1,}\.[a-z]{2,6})|(?(?:[0-9]{1,3}\.){3}[0-9]{1,3}))(?:[:](?[0-9]+))?(?:[\/](?[-a-zA-Z0-9'@:%_\+.,~#&\/=()]*[-a-zA-Z0-9@:%_\+.~#&\/=()])?)?(?:[?](?[-a-zA-Z0-9@:%_\+.~#&\/=()]*))*)/ig; +exports.VALID_URL_REGEX = VALID_URL_REGEX; + diff --git a/packages/common/src/utils.js b/packages/common/src/utils.js index 24b27a71b2..13f1e347e2 100644 --- a/packages/common/src/utils.js +++ b/packages/common/src/utils.js @@ -1,4 +1,21 @@ -const { GOAL_STATUS } = require('./constants'); +const { GOAL_STATUS, DISALLOWED_URLS, VALID_URL_REGEX } = require('./constants'); + +function isValidResourceUrl(attempted) { + try { + const httpOccurences = (attempted.match(/http/gi) || []).length; + if ( + httpOccurences !== 1 + || !VALID_URL_REGEX.test(attempted) + || DISALLOWED_URLS.some((disallowed) => disallowed.url === attempted) + ) { + return false; + } + const u = new URL(attempted); + return (u !== ''); + } catch (e) { + return false; + } +}; /** * Given a list of goal statuses, determine the final status @@ -31,5 +48,6 @@ function determineMergeGoalStatus(statuses) { } module.exports = { - determineMergeGoalStatus + determineMergeGoalStatus, + isValidResourceUrl, } \ No newline at end of file From 60fa0c6241ec0ead0c942ba488621b56feedb129 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 10:15:44 -0400 Subject: [PATCH 03/12] v2.1.4 --- packages/common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/package.json b/packages/common/package.json index 50743557b8..9c73b03e84 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@ttahub/common", - "version": "2.1.3", + "version": "2.1.4", "description": "The purpose of this package is to reduce code duplication between the frontend and backend projects.", "main": "src/index.js", "author": "", From b1cdcfef7127658ef6fb987b9074c4556f2b0dfa Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 11:11:24 -0400 Subject: [PATCH 04/12] Update isValidResourceUrl in common to bridge the gap between implemetations --- packages/common/src/utils.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/common/src/utils.js b/packages/common/src/utils.js index 13f1e347e2..0e7782aa94 100644 --- a/packages/common/src/utils.js +++ b/packages/common/src/utils.js @@ -5,11 +5,17 @@ function isValidResourceUrl(attempted) { const httpOccurences = (attempted.match(/http/gi) || []).length; if ( httpOccurences !== 1 - || !VALID_URL_REGEX.test(attempted) || DISALLOWED_URLS.some((disallowed) => disallowed.url === attempted) ) { return false; } + + const matches = [...attempted.matchAll(VALID_URL_REGEX)].map(({ groups }) => groups); + + if (matches?.length !== 1) { + return false; + } + const u = new URL(attempted); return (u !== ''); } catch (e) { From 83484d3c3435679591b6c97963bc29c3a75a688f Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 11:11:38 -0400 Subject: [PATCH 05/12] v2.1.5 --- packages/common/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/package.json b/packages/common/package.json index 9c73b03e84..54c8ba6aff 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@ttahub/common", - "version": "2.1.4", + "version": "2.1.5", "description": "The purpose of this package is to reduce code duplication between the frontend and backend projects.", "main": "src/index.js", "author": "", From 812cf46221503988b9e27a746d0ec2c2c7f63308 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 11:27:01 -0400 Subject: [PATCH 06/12] Update with common solution --- frontend/package.json | 2 +- .../components/GoalForm/ResourceRepeater.js | 7 +++---- frontend/src/components/GoalForm/constants.js | 20 +++++-------------- .../Pages/components/Objective.js | 6 +++--- .../components/SessionObjectiveResource.js | 2 +- frontend/src/pages/SessionForm/index.js | 3 +-- frontend/yarn.lock | 8 ++++---- package.json | 2 +- src/lib/urlUtils.js | 18 ----------------- src/lib/urlUtils.test.js | 5 ++++- src/models/hooks/resource.js | 2 +- src/services/resource.js | 2 +- yarn.lock | 10 +++++----- 13 files changed, 30 insertions(+), 57 deletions(-) delete mode 100644 src/lib/urlUtils.js diff --git a/frontend/package.json b/frontend/package.json index 435b09e172..bc4e507bfd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,7 @@ "@silevis/reactgrid": "3.1", "@react-hook/resize-observer": "^1.2.6", "@trussworks/react-uswds": "4.1.1", - "@ttahub/common": "^2.1.3", + "@ttahub/common": "^2.1.5", "@use-it/interval": "^1.0.0", "async": "^3.2.3", "browserslist": "^4.16.5", diff --git a/frontend/src/components/GoalForm/ResourceRepeater.js b/frontend/src/components/GoalForm/ResourceRepeater.js index f90c56a3a0..c4cd7ed5b7 100644 --- a/frontend/src/components/GoalForm/ResourceRepeater.js +++ b/frontend/src/components/GoalForm/ResourceRepeater.js @@ -11,7 +11,6 @@ import QuestionTooltip from './QuestionTooltip'; import URLInput from '../URLInput'; import colors from '../../colors'; import './ResourceRepeater.scss'; -import { OBJECTIVE_LINK_ERROR } from './constants'; export default function ResourceRepeater({ resources, @@ -44,7 +43,7 @@ export default function ResourceRepeater({ const updateResource = (value, i) => { const newResources = [...resources]; - const toUpdate = { ...newResources[i], value }; + const toUpdate = { ...newResources[i], value: value.trim() }; newResources.splice(i, 1, toUpdate); setResources(newResources); }; @@ -64,7 +63,7 @@ export default function ResourceRepeater({ Enter one resource per field. To enter more resources, select “Add new resource” - {error.props.children ? OBJECTIVE_LINK_ERROR : null} + {error.props.children ? error : null}
{ resources.map((r, i) => (
@@ -77,7 +76,7 @@ export default function ResourceRepeater({ id={`resource-${i + 1}`} onBlur={validateResources} onChange={({ target: { value } }) => updateResource(value, i)} - value={r.value} + value={r.value || ''} disabled={isLoading} /> { resources.length > 1 ? ( diff --git a/frontend/src/components/GoalForm/constants.js b/frontend/src/components/GoalForm/constants.js index de1565528d..3f10184645 100644 --- a/frontend/src/components/GoalForm/constants.js +++ b/frontend/src/components/GoalForm/constants.js @@ -1,22 +1,12 @@ import React from 'react'; import { v4 as uuidv4 } from 'uuid'; -import { DECIMAL_BASE } from '@ttahub/common'; +import { DECIMAL_BASE, DISALLOWED_URLS, isValidResourceUrl } from '@ttahub/common'; import { uniq } from 'lodash'; -// regex to match a valid url, it must start with http:// or https://, have at least one dot, and not end with a dot or a space -const VALID_URL_REGEX = /^https?:\/\/.*\.[^ |^.]/; - -export const isValidResourceUrl = (attempted) => { - try { - const httpOccurences = (attempted.match(/http/gi) || []).length; - if (httpOccurences !== 1 || !VALID_URL_REGEX.test(attempted)) { - return false; - } - const u = new URL(attempted); - return (u !== ''); - } catch (e) { - return false; - } +export const noDisallowedUrls = (value) => { + const urls = value.map((v) => v.value); + const disallowedUrl = DISALLOWED_URLS.find((disallowed) => urls.includes(disallowed.url)); + return disallowedUrl ? disallowedUrl.error : true; }; export const objectivesWithValidResourcesOnly = (objectives) => { diff --git a/frontend/src/pages/ActivityReport/Pages/components/Objective.js b/frontend/src/pages/ActivityReport/Pages/components/Objective.js index 368e939a2e..e73c7a873e 100644 --- a/frontend/src/pages/ActivityReport/Pages/components/Objective.js +++ b/frontend/src/pages/ActivityReport/Pages/components/Objective.js @@ -18,11 +18,10 @@ import { OBJECTIVE_PROP, NO_ERROR, ERROR_FORMAT } from './constants'; import { uploadObjectivesFile } from '../../../../fetchers/File'; import { OBJECTIVE_TITLE, - OBJECTIVE_RESOURCES, OBJECTIVE_TTA, OBJECTIVE_TOPICS, } from './goalValidator'; -import { validateListOfResources } from '../../../../components/GoalForm/constants'; +import { validateListOfResources, noDisallowedUrls } from '../../../../components/GoalForm/constants'; import AppLoadingContext from '../../../../AppLoadingContext'; import './Objective.scss'; import ObjectiveSuspendModal from '../../../../components/ObjectiveSuspendModal'; @@ -112,7 +111,8 @@ export default function Objective({ name: `${fieldArrayName}[${index}].resources`, rules: { validate: { - allResourcesAreValid: (value) => validateListOfResources(value) || OBJECTIVE_RESOURCES, + allResourcesAreValid: (value) => validateListOfResources(value) || 'Enter one resource per field. Valid resource links must start with http:// or https://', + noDisallowedUrls, }, }, defaultValue: objective.resources, diff --git a/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js b/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js index a7583df58c..054aea37cf 100644 --- a/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js +++ b/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { isValidResourceUrl } from '@ttahub/common'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; import { useFormContext } from 'react-hook-form'; @@ -12,7 +13,6 @@ import { FormGroup, } from '@trussworks/react-uswds'; import colors from '../../../colors'; -import { isValidResourceUrl } from '../../../components/GoalForm/constants'; import './SessionObjectiveResource.scss'; export default function SessionObjectiveResource({ diff --git a/frontend/src/pages/SessionForm/index.js b/frontend/src/pages/SessionForm/index.js index 73357201d0..52e322f395 100644 --- a/frontend/src/pages/SessionForm/index.js +++ b/frontend/src/pages/SessionForm/index.js @@ -10,7 +10,7 @@ import { Helmet } from 'react-helmet'; import { Alert, Grid } from '@trussworks/react-uswds'; import { useHistory, Redirect } from 'react-router-dom'; import { FormProvider, useForm } from 'react-hook-form'; -import { TRAINING_REPORT_STATUSES } from '@ttahub/common'; +import { TRAINING_REPORT_STATUSES, isValidResourceUrl } from '@ttahub/common'; import useSocket, { usePublishWebsocketLocationOnInterval } from '../../hooks/useSocket'; import useHookFormPageState from '../../hooks/useHookFormPageState'; import { defaultValues } from './constants'; @@ -21,7 +21,6 @@ import Navigator from '../../components/Navigator'; import BackLink from '../../components/BackLink'; import pages from './pages'; import AppLoadingContext from '../../AppLoadingContext'; -import { isValidResourceUrl } from '../../components/GoalForm/constants'; // websocket publish location interval const INTERVAL_DELAY = 10000; // TEN SECONDS diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 7f08389540..1a68234473 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2373,10 +2373,10 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== -"@ttahub/common@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@ttahub/common/-/common-2.1.3.tgz#56fa179bec3525d7bd322907e25fa123e30f4b7d" - integrity sha512-HwzTa0t4a7sFG9N4qOo3HkrWPFZVYrYFJf/dRwRuFze/+o36B9oXaz+TY7Aqj30An6I1njKdytYPnWNIaSQf7w== +"@ttahub/common@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@ttahub/common/-/common-2.1.5.tgz#1e9550c507a6e4422eae72a923eed7c18613e827" + integrity sha512-fy2jgE7xdaifZsxF+uiqwGpsxa0UchjPUNI2BI84Rw72FiTAglpkfn4wy4yO82NW7PyIa9CbICa4sP+XcKwKMg== "@turf/area@^6.4.0": version "6.5.0" diff --git a/package.json b/package.json index 7857d3fcc7..5c9d22b986 100644 --- a/package.json +++ b/package.json @@ -300,7 +300,7 @@ "@elastic/elasticsearch-mock": "^2.0.0", "@faker-js/faker": "^6.0.0", "@opensearch-project/opensearch": "^1.1.0", - "@ttahub/common": "^2.1.3", + "@ttahub/common": "^2.1.5", "adm-zip": "^0.5.1", "aws-sdk": "^2.826.0", "aws4": "^1.11.0", diff --git a/src/lib/urlUtils.js b/src/lib/urlUtils.js deleted file mode 100644 index 73f4084fff..0000000000 --- a/src/lib/urlUtils.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -/* eslint-disable no-useless-escape */ -// regex to match a valid url, it must start with http:// or https://, have at least one dot, and not end with a dot or a space -export const VALID_URL_REGEX = /(?(?http(?:s)?):\/\/(?:(?[a-zA-Z0-9._]+)(?:[:](?[a-zA-Z0-9%._\+~#=]+))?[@])?(?:(?:www\.)?(?[-a-zA-Z0-9%._\+~#=]{1,}\.[a-z]{2,6})|(?(?:[0-9]{1,3}\.){3}[0-9]{1,3}))(?:[:](?[0-9]+))?(?:[\/](?[-a-zA-Z0-9'@:%_\+.,~#&\/=()]*[-a-zA-Z0-9@:%_\+.~#&\/=()])?)?(?:[?](?[-a-zA-Z0-9@:%_\+.~#&\/=()]*))*)/ig; - -// regex and function copied from frontend (they should match) -export const isValidResourceUrl = (input) => { - try { - const matches = [...input.matchAll(VALID_URL_REGEX)].map(({ groups }) => groups); - if (matches?.length !== 1) { - return false; - } - const u = new URL(matches[0].url); - return (u !== ''); - } catch (e) { - return false; - } -}; diff --git a/src/lib/urlUtils.test.js b/src/lib/urlUtils.test.js index 6c27a05080..e3d73d0b56 100644 --- a/src/lib/urlUtils.test.js +++ b/src/lib/urlUtils.test.js @@ -1,4 +1,4 @@ -import { isValidResourceUrl } from './urlUtils'; +import { isValidResourceUrl } from '@ttahub/common'; describe('urlUtils', () => { describe('isValidResourceUrl', () => { @@ -22,6 +22,9 @@ describe('urlUtils', () => { 'http://google.comhttp://ask.comhttp://aol.com', // eslint-disable-next-line no-useless-escape 'http:\lkj http:/test.v', + 'https://eclkc.ohs.acf.hhs.gov/professional-development/individualized-professional-development-ipd-portfolio/individualized-professional-development-ipd-portfolio', + 'https://eclkc.ohs.acf.hhs.gov/cas/login', + 'https://eclkc.ohs.acf.hhs.gov/professional-development/individualized-professional-development-ipd-portfolio/course-catalog', ]; it('correctly validates resources', () => { diff --git a/src/models/hooks/resource.js b/src/models/hooks/resource.js index 12b2cf4d4e..a7dd8c4d9c 100644 --- a/src/models/hooks/resource.js +++ b/src/models/hooks/resource.js @@ -1,4 +1,4 @@ -import { VALID_URL_REGEX } from '../../lib/urlUtils'; +const { VALID_URL_REGEX } = require('@ttahub/common'); const autoPopulateDomain = (sequelize, instance, options) => { // eslint-disable-next-line no-prototype-builtins diff --git a/src/services/resource.js b/src/services/resource.js index 02f8421c30..2ea5b3a136 100644 --- a/src/services/resource.js +++ b/src/services/resource.js @@ -1,4 +1,5 @@ import { Op } from 'sequelize'; +import { VALID_URL_REGEX } from '@ttahub/common'; import { ActivityReport, ActivityReportResource, @@ -14,7 +15,6 @@ import { NextStepResource, Resource, } from '../models'; -import { VALID_URL_REGEX } from '../lib/urlUtils'; import { SOURCE_FIELD } from '../constants'; import Semaphore from '../lib/semaphore'; diff --git a/yarn.lock b/yarn.lock index 855ff743f5..8ed08520f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3275,10 +3275,10 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== -"@ttahub/common@^2.1.3": - version "2.1.3" - resolved "https://registry.yarnpkg.com/@ttahub/common/-/common-2.1.3.tgz#56fa179bec3525d7bd322907e25fa123e30f4b7d" - integrity sha512-HwzTa0t4a7sFG9N4qOo3HkrWPFZVYrYFJf/dRwRuFze/+o36B9oXaz+TY7Aqj30An6I1njKdytYPnWNIaSQf7w== +"@ttahub/common@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@ttahub/common/-/common-2.1.5.tgz#1e9550c507a6e4422eae72a923eed7c18613e827" + integrity sha512-fy2jgE7xdaifZsxF+uiqwGpsxa0UchjPUNI2BI84Rw72FiTAglpkfn4wy4yO82NW7PyIa9CbICa4sP+XcKwKMg== "@types/argparse@1.0.38": version "1.0.38" @@ -14239,4 +14239,4 @@ yayson@^2.1.0: optionalDependencies: lodash "^4.17.21" q "^1.5.1" - underscore "^1.13.1" \ No newline at end of file + underscore "^1.13.1" From 19b92620201a61ba5130e5de9ee9ae1b34acc1fe Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 11:34:00 -0400 Subject: [PATCH 07/12] Add an additional test --- .../GoalForm/__tests__/constants.js | 146 ++++++++++-------- 1 file changed, 83 insertions(+), 63 deletions(-) diff --git a/frontend/src/components/GoalForm/__tests__/constants.js b/frontend/src/components/GoalForm/__tests__/constants.js index d998999b3a..4fe3398d00 100644 --- a/frontend/src/components/GoalForm/__tests__/constants.js +++ b/frontend/src/components/GoalForm/__tests__/constants.js @@ -4,84 +4,104 @@ import { FORM_FIELD_INDEXES, objectivesWithValidResourcesOnly, grantsToMultiValue, + noDisallowedUrls, } from '../constants'; describe('form constants', () => { it('the amount of form fields and the amount of default errors should match', () => { expect(Object.keys(FORM_FIELD_INDEXES).length).toBe(FORM_FIELD_DEFAULT_ERRORS.length); }); -}); -describe('objectivesWithValidResourcesOnly', () => { - it('strips invalid resources', () => { - const objectives = [ - { - resources: [ - { value: 'https://www.google.com' }, - { value: 'not a valid url' }, - { value: 'https://www.google.com' }, - { value: 'https://www.google.com ' }, - { value: ' https://www.google.com' }, - ], - }, + describe('noDisallowedUrls', () => { + const goodUrls = [{ value: 'https://www.google.com' }]; + + const badUrls = [ + 'https://eclkc.ohs.acf.hhs.gov/professional-development/individualized-professional-development-ipd-portfolio/course-catalog', + 'https://eclkc.ohs.acf.hhs.gov/professional-development/individualized-professional-development-ipd-portfolio/individualized-professional-development-ipd-portfolio', + 'https://eclkc.ohs.acf.hhs.gov/cas/login', ]; - expect(objectivesWithValidResourcesOnly(objectives)).toEqual([ - { - resources: [ - { value: 'https://www.google.com' }, - { value: 'https://www.google.com' }, - { value: 'https://www.google.com' }, - { value: 'https://www.google.com' }, - ], - }, - ]); + it('returns true if the url is not in the disallowed list', () => { + expect(noDisallowedUrls(goodUrls)).toBe(true); + }); + + it('returns false if the url is in the disallowed list', () => { + badUrls.forEach((url) => { + expect(noDisallowedUrls([{ value: url }])).toBe('This link is no longer accepted in this field. Enter iPD courses used during your TTA session in the other field in this section.'); + }); + }); }); -}); -describe('validateListOfResources', () => { - it('returns false if there is an invalid resource', () => { - expect(validateListOfResources([{ value: 'http://www.test-domain.com/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain' }])).toBe(true); - expect(validateListOfResources([{ value: 'https://test.com' }])).toBe(true); - expect(validateListOfResources([{ value: 'http://test.com' }])).toBe(true); - expect(validateListOfResources([{ value: 'https://www.test.com' }])).toBe(true); - expect(validateListOfResources([{ value: 'http://www.test.com' }])).toBe(true); - expect(validateListOfResources([{ value: 'file://test.com' }])).toBe(false); - expect(validateListOfResources([{ value: 'http://test' }])).toBe(false); - expect(validateListOfResources([{ value: 'https://test' }])).toBe(false); - expect(validateListOfResources([{ value: 'http:mickeymouse.com' }])).toBe(false); - expect(validateListOfResources([{ value: 'http://google.comhttp://ask.comhttp://aol.com' }])).toBe(false); - expect(validateListOfResources([{ value: ' https://eclkc.ohs.acf.hhs.gov/sites/default/files/pdf/healthy-children-ready-learn.pdf cf.hhs.gov/policy/45-cfr-chap-xiii/1302-subpart-d-health-program-services •\tHealth Competencies https://eclkc.ohs.acf.hhs.gov/sites/default/files/pdf/health-competencies.pdf Non-ECLKC resources\t https://nrckids.org/CFOC/ https://ufhealth.org/well-child-visits#:~:text=15%20months,2%201%2F2%20years",117689' }])).toBe(false); - // eslint-disable-next-line no-useless-escape - expect(validateListOfResources([{ value: 'http:\lkj http:/test.v' }])).toBe(false); - expect(validateListOfResources([ - { value: 'https://www.google.com' }, - { value: 'not a valid url' }, - { value: 'https://www.google.com' }, - ])).toBe(false); + describe('validateListOfResources', () => { + it('returns false if there is an invalid resource', () => { + expect(validateListOfResources([{ value: 'http://www.test-domain.com/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain/long-domain/long/long/domain' }])).toBe(true); + expect(validateListOfResources([{ value: 'https://test.com' }])).toBe(true); + expect(validateListOfResources([{ value: 'http://test.com' }])).toBe(true); + expect(validateListOfResources([{ value: 'https://www.test.com' }])).toBe(true); + expect(validateListOfResources([{ value: 'http://www.test.com' }])).toBe(true); + expect(validateListOfResources([{ value: 'file://test.com' }])).toBe(false); + expect(validateListOfResources([{ value: 'http://test' }])).toBe(false); + expect(validateListOfResources([{ value: 'https://test' }])).toBe(false); + expect(validateListOfResources([{ value: 'http:mickeymouse.com' }])).toBe(false); + expect(validateListOfResources([{ value: 'http://google.comhttp://ask.comhttp://aol.com' }])).toBe(false); + expect(validateListOfResources([{ value: ' https://eclkc.ohs.acf.hhs.gov/sites/default/files/pdf/healthy-children-ready-learn.pdf cf.hhs.gov/policy/45-cfr-chap-xiii/1302-subpart-d-health-program-services •\tHealth Competencies https://eclkc.ohs.acf.hhs.gov/sites/default/files/pdf/health-competencies.pdf Non-ECLKC resources\t https://nrckids.org/CFOC/ https://ufhealth.org/well-child-visits#:~:text=15%20months,2%201%2F2%20years",117689' }])).toBe(false); + // eslint-disable-next-line no-useless-escape + expect(validateListOfResources([{ value: 'http:\lkj http:/test.v' }])).toBe(false); + expect(validateListOfResources([ + { value: 'https://www.google.com' }, + { value: 'not a valid url' }, + { value: 'https://www.google.com' }, + ])).toBe(false); + }); }); -}); -test('grantsToSources function should return the correct source object', () => { - const grants = [ - { numberWithProgramTypes: '123' }, - { numberWithProgramTypes: '456' }, - { numberWithProgramTypes: '789' }, - ]; + test('grantsToSources function should return the correct source object', () => { + const grants = [ + { numberWithProgramTypes: '123' }, + { numberWithProgramTypes: '456' }, + { numberWithProgramTypes: '789' }, + ]; + + const source = { + 123: 'Source 1', + 456: 'Source 2', + 1234: 'Source 1', + }; - const source = { - 123: 'Source 1', - 456: 'Source 2', - 1234: 'Source 1', - }; + const expected = { + 123: 'Source 1', + 456: 'Source 2', + 789: '', + }; - const expected = { - 123: 'Source 1', - 456: 'Source 2', - 789: '', - }; + const result = grantsToMultiValue(grants, source); - const result = grantsToMultiValue(grants, source); + expect(result).toEqual(expected); + }); + describe('objectivesWithValidResourcesOnly', () => { + it('strips invalid resources', () => { + const objectives = [ + { + resources: [ + { value: 'https://www.google.com' }, + { value: 'not a valid url' }, + { value: 'https://www.google.com' }, + { value: 'https://www.google.com ' }, + { value: ' https://www.google.com' }, + ], + }, + ]; - expect(result).toEqual(expected); + expect(objectivesWithValidResourcesOnly(objectives)).toEqual([ + { + resources: [ + { value: 'https://www.google.com' }, + { value: 'https://www.google.com' }, + { value: 'https://www.google.com' }, + { value: 'https://www.google.com' }, + ], + }, + ]); + }); + }); }); From 9c3ee6d50e526a9374de96608f14894b2c2d6189 Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Tue, 18 Jun 2024 12:22:54 -0400 Subject: [PATCH 08/12] Show correct error on Session Objective --- .../pages/SessionForm/components/SessionObjectiveResource.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js b/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js index 054aea37cf..a1c7390f85 100644 --- a/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js +++ b/frontend/src/pages/SessionForm/components/SessionObjectiveResource.js @@ -13,6 +13,7 @@ import { FormGroup, } from '@trussworks/react-uswds'; import colors from '../../../colors'; +import { noDisallowedUrls } from '../../../components/GoalForm/constants'; import './SessionObjectiveResource.scss'; export default function SessionObjectiveResource({ @@ -46,6 +47,7 @@ export default function SessionObjectiveResource({ validate: { isValidResourceUrl: (value) => { if (!value) return true; + if (noDisallowedUrls([{ value }]) !== true) return noDisallowedUrls([{ value }]); return isValidResourceUrl(value) || 'Please enter a valid URL'; }, }, From 9b9934370bad76ea266f152c240daf0b8ab90b1e Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 20 Jun 2024 10:06:14 -0400 Subject: [PATCH 09/12] Flip validation order --- frontend/src/pages/ActivityReport/Pages/components/Objective.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/ActivityReport/Pages/components/Objective.js b/frontend/src/pages/ActivityReport/Pages/components/Objective.js index e73c7a873e..5a9399919a 100644 --- a/frontend/src/pages/ActivityReport/Pages/components/Objective.js +++ b/frontend/src/pages/ActivityReport/Pages/components/Objective.js @@ -111,8 +111,8 @@ export default function Objective({ name: `${fieldArrayName}[${index}].resources`, rules: { validate: { - allResourcesAreValid: (value) => validateListOfResources(value) || 'Enter one resource per field. Valid resource links must start with http:// or https://', noDisallowedUrls, + allResourcesAreValid: (value) => validateListOfResources(value) || 'Enter one resource per field. Valid resource links must start with http:// or https://', }, }, defaultValue: objective.resources, From bea8193bd8ba0cddef505735c2fcf20617cb494c Mon Sep 17 00:00:00 2001 From: Matt Bevilacqua Date: Thu, 20 Jun 2024 11:02:53 -0400 Subject: [PATCH 10/12] Improve error display generally, and deploy to dev --- .circleci/config.yml | 2 +- .../components/GoalForm/ResourceRepeater.js | 19 ++----- .../components/GoalForm/ResourceRepeater.scss | 2 +- .../GoalForm/__tests__/ResourceRepeater.js | 57 ++++++++++++++----- 4 files changed, 51 insertions(+), 29 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 738a9c1490..28b541b1d0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -424,7 +424,7 @@ parameters: type: string dev_git_branch: # change to feature branch to test deployment description: "Name of github branch that will deploy to dev" - default: "lr/ttahub-1521/display-regions-in-AR-header" + default: "lmb/TTAHUB-3007/no-disallowed-urls" type: string sandbox_git_branch: # change to feature branch to test deployment default: "al-ttahub-2726-goals-and-objectives-review-redesign" diff --git a/frontend/src/components/GoalForm/ResourceRepeater.js b/frontend/src/components/GoalForm/ResourceRepeater.js index c4cd7ed5b7..fc5756728f 100644 --- a/frontend/src/components/GoalForm/ResourceRepeater.js +++ b/frontend/src/components/GoalForm/ResourceRepeater.js @@ -1,4 +1,5 @@ import React from 'react'; +import { isValidResourceUrl } from '@ttahub/common'; import PropTypes from 'prop-types'; import { v4 as uuidv4 } from 'uuid'; import { @@ -18,10 +19,12 @@ export default function ResourceRepeater({ error, validateResources, toolTipText, - validateOnRemove, isLoading, }) { const addResource = () => { + if ((error.props.children) || resources.some((r) => !r.value)) { + return; + } const newResources = [...resources, { key: uuidv4(), value: '' }]; setResources(newResources); }; @@ -30,15 +33,7 @@ export default function ResourceRepeater({ const newResources = [...resources]; newResources.splice(i, 1); setResources(newResources); - - // This is an attempt to handle on remove validation for resources. - // the AR and RTR use two different approaches to validation. - // This works around it by allowing the parent component to pass in a validation function. - if (validateOnRemove) { - validateOnRemove(newResources); - } else { - validateResources(); - } + validateResources(); }; const updateResource = (value, i) => { @@ -66,7 +61,7 @@ export default function ResourceRepeater({ {error.props.children ? error : null}
{ resources.map((r, i) => ( -
+