diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 56e8448b0c..0cf9e1d9d5 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -57,6 +57,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f # tag=v3.25.7 + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 # tag=v3.25.10 with: sarif_file: results.sarif diff --git a/.github/workflows/test-checks.yaml b/.github/workflows/test-checks.yaml index 5f128e5e52..e9a9dd0b94 100644 --- a/.github/workflows/test-checks.yaml +++ b/.github/workflows/test-checks.yaml @@ -41,7 +41,7 @@ jobs: ignore-unfixed: false severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 with: sarif_file: 'trivy-results.sarif' @@ -61,13 +61,13 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@f079b8493333aace61c81488f8bd40919487bd9f + uses: github/codeql-action/init@23acc5c183826b7a8a97bce3cecc52db901f8251 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@f079b8493333aace61c81488f8bd40919487bd9f + uses: github/codeql-action/autobuild@23acc5c183826b7a8a97bce3cecc52db901f8251 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f079b8493333aace61c81488f8bd40919487bd9f + uses: github/codeql-action/analyze@23acc5c183826b7a8a97bce3cecc52db901f8251 cocogitto: runs-on: ubuntu-latest @@ -92,7 +92,7 @@ jobs: - uses: actions/checkout@v4 - run: ./.bin/install-gitleaks-linux-x64.sh - run: ./gitleaks detect --exit-code 0 --report-format sarif --report-path "gitleaks.sarif" - - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f + - uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 with: sarif_file: 'gitleaks.sarif' diff --git a/.github/workflows/test-code.yaml b/.github/workflows/test-code.yaml index 8c53f1430c..b092a2418a 100644 --- a/.github/workflows/test-code.yaml +++ b/.github/workflows/test-code.yaml @@ -59,7 +59,7 @@ jobs: fs.writeFileSync('results.sarif', JSON.stringify(sarifJson, null, 2)); EOF working-directory: ./app - - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f + - uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 with: sarif_file: app/results.sarif diff --git a/.github/workflows/test-containers.yaml b/.github/workflows/test-containers.yaml index 5cac7b4619..2bd0112a0f 100644 --- a/.github/workflows/test-containers.yaml +++ b/.github/workflows/test-containers.yaml @@ -37,7 +37,7 @@ jobs: severity: CRITICAL timeout: 10m0s - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 with: sarif_file: 'trivy-results.sarif' - name: Get Results Length From Sarif @@ -64,7 +64,7 @@ jobs: severity: CRITICAL timeout: 10m0s - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@f079b8493333aace61c81488f8bd40919487bd9f + uses: github/codeql-action/upload-sarif@23acc5c183826b7a8a97bce3cecc52db901f8251 with: sarif_file: 'trivy-results.sarif' - name: Get Results Length From Sarif diff --git a/app/Dockerfile b/app/Dockerfile index 8087788e1a..016ea37128 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.access.redhat.com/ubi9/s2i-base@sha256:9af1318d4d016b4aab0723aafdaf739972065919559107eadaccac4c8f350f37 +FROM registry.access.redhat.com/ubi9/s2i-base@sha256:798d3d983bde082429eaced15475722585c994612ba062dbce6428877ee3ca32 ENV SUMMARY="An image for the CONN-CCBC-portal app" \ DESCRIPTION="This image contains the compiled CONN-CCBC-portal node app" diff --git a/app/components/Analyst/RFI/RFIAnalystUpload.tsx b/app/components/Analyst/RFI/RFIAnalystUpload.tsx index 882da3027e..832a689593 100644 --- a/app/components/Analyst/RFI/RFIAnalystUpload.tsx +++ b/app/components/Analyst/RFI/RFIAnalystUpload.tsx @@ -13,6 +13,8 @@ import styled from 'styled-components'; import { useCreateNewFormDataMutation } from 'schema/mutations/application/createNewFormData'; import useHHCountUpdateEmail from 'lib/helpers/useHHCountUpdateEmail'; import useRfiCoverageMapKmzUploadedEmail from 'lib/helpers/useRfiCoverageMapKmzUploadedEmail'; +import { useToast } from 'components/AppProvider'; +import Link from 'next/link'; const Flex = styled('header')` display: flex; @@ -20,6 +22,10 @@ const Flex = styled('header')` width: 100%; `; +const StyledLink = styled(Link)` + color: ${(props) => props.theme.color.white}; +`; + const RfiAnalystUpload = ({ query }) => { const queryFragment = useFragment( graphql` @@ -52,6 +58,7 @@ const RfiAnalystUpload = ({ query }) => { rowId: applicationId, ccbcNumber, } = applicationByRowId; + const { showToast, hideToast } = useToast(); const { rfiNumber } = rfiDataByRowId; @@ -94,9 +101,30 @@ const RfiAnalystUpload = ({ query }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [templateData]); + const getToastMessage = () => { + return ( + <> + {' '} + `Template {templateData?.templateNumber} data changed successfully, new + values for{' '} + {templateData?.templateNumber === 1 + ? 'Total Households and Indigenous Households' + : 'Total eligible costs and Total project costs'}{' '} + data in the application now reflect template uploads. Please see{' '} + + history page + {' '} + for details.` + + ); + }; + const handleSubmit = () => { const updatedExcelFields = excelImportFields.join(', '); const reasonForChange = `Auto updated from upload of ${updatedExcelFields} for RFI: ${rfiNumber}`; + hideToast(); updateRfi({ variables: { input: { @@ -129,6 +157,12 @@ const RfiAnalystUpload = ({ query }) => { } ); } + if ( + templateData?.templateNumber === 1 || + templateData?.templateNumber === 2 + ) { + showToast(getToastMessage(), 'success', 100000000); + } }, }); } diff --git a/app/components/AppProvider.tsx b/app/components/AppProvider.tsx new file mode 100644 index 0000000000..91ccd6d17b --- /dev/null +++ b/app/components/AppProvider.tsx @@ -0,0 +1,63 @@ +import React, { + createContext, + useContext, + useState, + useCallback, + useMemo, + ReactNode, +} from 'react'; +import Toast, { ToastType } from 'components/Toast'; + +type AppContextType = { + showToast?: (message: ReactNode, type?: ToastType, timeout?: number) => void; + hideToast?: () => void; +}; + +const AppContext = createContext({}); + +export const useToast = () => { + return useContext(AppContext); +}; + +export const AppProvider = ({ children }) => { + /** + * handling global toast messages + */ + const [toast, setToast] = useState<{ + visible: boolean; + message?: ReactNode; + type?: ToastType; + timeout?: number; + }>({ visible: false }); + + const showToast = useCallback( + ( + message: ReactNode, + type: ToastType = 'success', + timeout: number = 10000 + ) => { + setToast({ visible: true, message, type, timeout }); + }, + [] + ); + + const hideToast = useCallback(() => { + setToast({ visible: false }); + }, []); + + const contextValue = useMemo( + () => ({ showToast, hideToast }), + [showToast, hideToast] + ); + + return ( + + {children} + {toast?.visible && ( + + {toast.message} + + )} + + ); +}; diff --git a/app/components/Toast.tsx b/app/components/Toast.tsx index d14edf9f04..c1f8c6333a 100644 --- a/app/components/Toast.tsx +++ b/app/components/Toast.tsx @@ -9,7 +9,7 @@ import { import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { theme } from 'styles/GlobalTheme'; -type ToastType = 'success' | 'warning' | 'error'; +export type ToastType = 'success' | 'warning' | 'error'; type ToastDirection = 'left' | 'right'; diff --git a/app/lib/theme/widgets/FileWidget.tsx b/app/lib/theme/widgets/FileWidget.tsx index c0b7bca6ad..9dd1a5bcb7 100644 --- a/app/lib/theme/widgets/FileWidget.tsx +++ b/app/lib/theme/widgets/FileWidget.tsx @@ -13,6 +13,8 @@ import bytesToSize from 'utils/bytesToText'; import FileComponent from 'lib/theme/components/FileComponent'; import useDisposeOnRouteChange from 'lib/helpers/useDisposeOnRouteChange'; import { DateTime } from 'luxon'; +import { useToast } from 'components/AppProvider'; +import { ToastType } from 'components/Toast'; type File = { id: string | number; @@ -65,6 +67,7 @@ const FileWidget: React.FC = ({ const maxFileSizeInBytes = 104857600; const fileId = isFiles && value[0].id; const { setTemplateData } = formContext; + const { showToast, hideToast } = useToast(); useEffect(() => { if (rawErrors?.length > 0) { @@ -73,6 +76,7 @@ const FileWidget: React.FC = ({ }, [rawErrors, setErrors]); const getValidatedFile = async (file: any, formId: number) => { + let isTemplateValid = true; if (templateValidate) { const fileFormData = new FormData(); if (file) { @@ -92,6 +96,8 @@ const FileWidget: React.FC = ({ data, }); }); + } else { + isTemplateValid = false; } }); } @@ -109,6 +115,7 @@ const FileWidget: React.FC = ({ } return { + isTemplateValid, input: { attachment: { file, @@ -153,6 +160,19 @@ const FileWidget: React.FC = ({ }); }; + const showToastMessage = (files, type: ToastType = 'success') => { + const fields = + templateNumber === 1 + ? 'Total Households and Indigenous Households data' + : 'Total eligible costs and Total project costs data'; + const message = + type === 'success' + ? `Template ${templateNumber} validation successful, new values for ${fields} data in the application will update upon 'Save'` + : `Template ${templateNumber} validation failed: ${files.join(', ')} did not validate due to formatting issues. ${fields} in the application will not update.`; + + showToast(message, type, 100000000); + }; + const handleChange = async (e: React.ChangeEvent) => { const transaction = Sentry.startTransaction({ name: 'ccbc.function' }); const span = transaction.startChild({ @@ -161,6 +181,7 @@ const FileWidget: React.FC = ({ }); if (loading) return; + hideToast(); const formId = parseInt(router?.query?.id as string, 10) || parseInt(router?.query?.applicationId as string, 10); @@ -177,6 +198,8 @@ const FileWidget: React.FC = ({ const validatedFiles = resp.filter((file) => file.input); setErrors(resp.filter((file) => file.error)); + const validationErrors = resp.filter((file) => !file.isTemplateValid); + const uploadResponse = await Promise.all( validatedFiles.map(async (payload) => handleUpload(payload)) ); @@ -190,6 +213,20 @@ const FileWidget: React.FC = ({ } else { span.setStatus('ok'); } + + if (templateValidate) { + if (validationErrors.length > 0) { + showToastMessage( + validationErrors.map( + (error) => error.fileName || error.input?.attachment?.fileName + ), + 'error' + ); + } else if (validationErrors.length === 0 && uploadErrors.length === 0) { + showToastMessage(fileDetails.map((file) => file.name)); + } + } + span.finish(); transaction.finish(); diff --git a/app/package.json b/app/package.json index 6ae3d62528..ca6269be92 100644 --- a/app/package.json +++ b/app/package.json @@ -65,7 +65,7 @@ "connect-pg-simple": "^7.0.0", "convict": "^6.2.4", "cookie-parser": "^1.4.6", - "dayjs": "^1.11.8", + "dayjs": "^1.11.11", "debug": "^4.1.7", "delay": "^5.0.0", "dotenv": "^16.3.1", @@ -151,7 +151,7 @@ "eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-relay": "^1.8.3", "fse": "^4.0.1", - "happo-cypress": "^4.1.1", + "happo-cypress": "^4.2.0", "happo-e2e": "^2.6.0", "happo.io": "^8.3.3", "jest": "^28.1.0", diff --git a/app/pages/_app.tsx b/app/pages/_app.tsx index 0736cdfc67..ae8816b045 100644 --- a/app/pages/_app.tsx +++ b/app/pages/_app.tsx @@ -18,6 +18,7 @@ import GlobalStyle from 'styles/GobalStyles'; import GlobalTheme from 'styles/GlobalTheme'; import BCGovTypography from 'components/BCGovTypography'; import { SessionExpiryHandler } from 'components'; +import { AppProvider } from 'components/AppProvider'; config.autoAddCss = false; @@ -94,8 +95,10 @@ const MyApp = ({ Component, pageProps }: AppProps) => { }> - {typeof window !== 'undefined' && } - {component} + + {typeof window !== 'undefined' && } + {component} + diff --git a/app/tests/components/Analyst/RFI/RFIAnalystUpload.test.ts b/app/tests/components/Analyst/RFI/RFIAnalystUpload.test.ts index 2ef65710e1..c47c6179b4 100644 --- a/app/tests/components/Analyst/RFI/RFIAnalystUpload.test.ts +++ b/app/tests/components/Analyst/RFI/RFIAnalystUpload.test.ts @@ -200,6 +200,8 @@ describe('The RFIAnalystUpload component', () => { expect(screen.getByText('template_one.xlsx')).toBeInTheDocument(); + expect(screen.getByText(/Template 1 validation successful/)).toBeVisible(); + const saveButton = screen.getByRole('button', { name: 'Save', }); @@ -318,5 +320,160 @@ describe('The RFIAnalystUpload component', () => { ); expect(mockNotifyRfiCoverageMapKmzUploaded).toHaveBeenCalledTimes(1); + expect( + screen.getByText(/Template 1 data changed successfully/) + ).toBeVisible(); + }); + + it('should render success toast for template two when upload successful', async () => { + componentTestingHelper.loadQuery(); + componentTestingHelper.renderComponent(); + + // @ts-ignore + global.fetch = jest.fn(() => + Promise.resolve({ + ok: true, + status: 200, + json: () => + Promise.resolve({ + result: { + totalEligibleCosts: 92455, + totalProjectCosts: 101230, + }, + }), + }) + ); + + const dateInput = screen.getAllByPlaceholderText('YYYY-MM-DD')[0]; + + await act(async () => { + fireEvent.change(dateInput, { + target: { + value: '2025-07-01', + }, + }); + }); + + const file = new File([new ArrayBuffer(1)], 'template_two.xlsx', { + type: 'application/excel', + }); + + const inputFile = screen.getAllByTestId('file-test')[1]; + + await act(async () => { + fireEvent.change(inputFile, { target: { files: [file] } }); + }); + + componentTestingHelper.expectMutationToBeCalled( + 'createAttachmentMutation', + { + input: { + attachment: { + file: expect.anything(), + fileName: 'template_two.xlsx', + fileSize: '1 Bytes', + fileType: 'application/excel', + applicationId: expect.anything(), + }, + }, + } + ); + + await act(async () => { + componentTestingHelper.environment.mock.resolveMostRecentOperation({ + data: { + createAttachment: { + attachment: { + rowId: 1, + file: 'string', + }, + }, + }, + }); + }); + + expect(screen.getByText('template_two.xlsx')).toBeInTheDocument(); + + expect(screen.getByText(/Template 2 validation successful/)).toBeVisible(); + + const saveButton = screen.getByRole('button', { + name: 'Save', + }); + + await act(async () => { + fireEvent.click(saveButton); + }); + + componentTestingHelper.expectMutationToBeCalled( + 'updateWithTrackingRfiMutation', + { + input: { + jsonData: { + rfiType: [], + rfiAdditionalFiles: { + detailedBudgetRfi: true, + eligibilityAndImpactsCalculatorRfi: true, + detailedBudget: expect.anything(), + geographicCoverageMapRfi: true, + geographicCoverageMap: expect.anything(), + }, + }, + rfiRowId: 1, + }, + } + ); + + act(() => { + componentTestingHelper.environment.mock.resolveMostRecentOperation({ + data: { + updateWithTrackingRfi: { + rfiData: { + rowId: 1, + jsonData: { + rfiAdditionalFiles: { + detailedBudgetRfi: true, + eligibilityAndImpactsCalculatorRfi: true, + detailedBudget: expect.anything(), + geographicCoverageMapRfi: true, + geographicCoverageMap: expect.anything(), + }, + }, + }, + }, + }, + }); + }); + + componentTestingHelper.expectMutationToBeCalled( + 'createNewFormDataMutation', + { + input: { + applicationRowId: 1, + jsonData: { + benefits: { + householdsImpactedIndigenous: 13, + numberOfHouseholds: 12, + }, + budgetDetails: { + totalEligibleCosts: 92455, + totalProjectCost: 101230, + }, + }, + reasonForChange: + 'Auto updated from upload of Template 2 for RFI: RFI-01', + formSchemaId: 1, + }, + } + ); + + act(() => { + componentTestingHelper.environment.mock.resolveMostRecentOperation({ + data: {}, + }); + }); + + expect( + screen.getByText(/Template 2 data changed successfully/) + ).toBeVisible(); }); }); diff --git a/app/tests/components/Form/FileWidget.test.tsx b/app/tests/components/Form/FileWidget.test.tsx index 6ce3a775c6..108c70c09d 100644 --- a/app/tests/components/Form/FileWidget.test.tsx +++ b/app/tests/components/Form/FileWidget.test.tsx @@ -52,6 +52,7 @@ const componentTestingHelper = new ComponentTestingHelper({ application: data.application, pageNumber: getFormPage(uiSchema['ui:order'], 'coverage'), query: data.query, + formContext: { setTemplateData: jest.fn() }, }), }); @@ -535,6 +536,55 @@ describe('The FileWidget', () => { { body: formData, method: 'POST' } ); }); + + it('displays an error toast when template validation fails', async () => { + componentTestingHelper.loadQuery(); + componentTestingHelper.renderComponent((data) => ({ + application: data.application, + pageNumber: 11, + query: data.query, + })); + + const mockFetchPromiseTemplateOne = Promise.resolve({ + json: () => Promise.resolve(null), + }); + + global.fetch = jest.fn(() => { + return mockFetchPromiseTemplateOne; + }); + + const file = new File([new ArrayBuffer(1)], 'file.xlsx', { + type: 'application/vnd.ms-excel', + }); + + const inputFile = screen.getAllByTestId('file-test')[0]; + await act(async () => { + fireEvent.change(inputFile, { target: { files: [file] } }); + }); + const formData = new FormData(); + formData.append('file', file); + expect(global.fetch).toHaveBeenCalledOnce(); + expect(global.fetch).toHaveBeenCalledWith( + '/api/applicant/template?templateNumber=1', + { body: formData, method: 'POST' } + ); + + await act(async () => { + componentTestingHelper.environment.mock.resolveMostRecentOperation({ + data: { + createAttachment: { + attachment: { + rowId: 1, + file: 'string', + }, + }, + }, + }); + }); + + expect(screen.getByText(/Template 1 validation failed/)).toBeVisible(); + }); + afterEach(() => { jest.clearAllMocks(); }); diff --git a/app/tests/utils/componentTestingHelper.tsx b/app/tests/utils/componentTestingHelper.tsx index ce2fc8871b..df545a65a8 100644 --- a/app/tests/utils/componentTestingHelper.tsx +++ b/app/tests/utils/componentTestingHelper.tsx @@ -10,6 +10,7 @@ import { MockResolvers } from 'relay-test-utils/lib/RelayMockPayloadGenerator'; import GlobalTheme from 'styles/GlobalTheme'; import GlobalStyle from 'styles/GobalStyles'; import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime'; +import { AppProvider } from 'components/AppProvider'; import TestingHelper from './TestingHelper'; interface ComponentTestingHelperOptions { @@ -85,10 +86,12 @@ class ComponentTestingHelper< - + + + diff --git a/app/tests/utils/pageTestingHelper.tsx b/app/tests/utils/pageTestingHelper.tsx index 4ca685c3bb..0fc9d94a6c 100644 --- a/app/tests/utils/pageTestingHelper.tsx +++ b/app/tests/utils/pageTestingHelper.tsx @@ -10,6 +10,7 @@ import { ConcreteRequest, OperationType } from 'relay-runtime'; import { MockResolvers } from 'relay-test-utils/lib/RelayMockPayloadGenerator'; import GlobalTheme from 'styles/GlobalTheme'; import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime'; +import { AppProvider } from 'components/AppProvider'; import TestingHelper from './TestingHelper'; interface PageTestingHelperOptions { @@ -66,10 +67,12 @@ class PageTestingHelper extends TestingHelper { - + + + diff --git a/app/yarn.lock b/app/yarn.lock index 51e12c331c..ebcd4f50a0 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -6491,11 +6491,11 @@ brace-expansion@^2.0.1: balanced-match "^1.0.0" braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" browser-process-hrtime@^1.0.0: version "1.0.0" @@ -7369,15 +7369,10 @@ data-urls@^3.0.1: whatwg-mimetype "^3.0.0" whatwg-url "^11.0.0" -dayjs@^1.10.4: - version "1.11.2" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.2.tgz#fa0f5223ef0d6724b3d8327134890cfe3d72fbe5" - integrity sha512-F4LXf1OeU9hrSYRPTTj/6FbO4HTjPKXvEIC1P2kcnFurViINCVk3ZV0xAS3XVx9MkMsXbbqlK6hjseaYbgKEHw== - -dayjs@^1.11.8: - version "1.11.8" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea" - integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ== +dayjs@^1.10.4, dayjs@^1.11.11: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== debug@2.6.9: version "2.6.9" @@ -8597,10 +8592,10 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -9185,10 +9180,10 @@ gray-percentage@^2.0.0: resolved "https://registry.yarnpkg.com/gray-percentage/-/gray-percentage-2.0.0.tgz#b72a274d1b1379104a0050b63b207dc53fe56f99" integrity sha1-tyonTRsTeRBKAFC2OyB9xT/lb5k= -happo-cypress@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/happo-cypress/-/happo-cypress-4.1.1.tgz#d10f064fad72a6a733a22f2baa234518e582b6bc" - integrity sha512-qMSjOOlCGjGis+i/VTzcL8/2te73I2jrNcQkx+rJgcbgYV7x13tiEVVo/MrR5lCg7kHtXiNNXAXgw7XUxNh3pQ== +happo-cypress@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/happo-cypress/-/happo-cypress-4.2.0.tgz#899c6e2e16af197e8b65a89b6f056c541df6be7f" + integrity sha512-58OheJOeZpfXE78FkMJ4qv0LI5IjUcPyUaDrBAKkqlgAYtt6ZhCYSUH2V3AYDIIz+tWqAE4roF93/5sueggy5Q== happo-e2e@^2.6.0: version "2.6.0" diff --git a/yarn.lock b/yarn.lock index 468b63427b..6aa65220aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -406,11 +406,11 @@ brace-expansion@^1.1.7: concat-map "0.0.1" braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" buffer-from@^1.0.0: version "1.1.2" @@ -1140,10 +1140,10 @@ file-uri-to-path@2: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1"