From f383c625cc5db64219cfc26432a931110572248d Mon Sep 17 00:00:00 2001 From: Meissa Dia Date: Wed, 12 Aug 2020 14:02:26 -0600 Subject: [PATCH 01/20] Allow websockets in nginx config --- nginx/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 292ef878b..bc36348c6 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -23,7 +23,7 @@ http { add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload'; # CSP - add_header Content-Security-Policy "default-src 'self' blob:; script-src 'self' 'unsafe-inline' blob: data: https://tagmanager.google.com https://www.googletagmanager.com https://www.google-analytics.com https://*.cfpb.gov https://www.consumerfinance.gov; img-src 'self' blob: data: https://www.google-analytics.com https://raw.githubusercontent.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; frame-src 'self' https://www.youtube.com/ https://ffiec.cfpb.gov/; connect-src 'self' https://*.cfpb.gov https://www.consumerfinance.gov https://raw.githubusercontent.com https://ffiec-api.cfpb.gov https://ffiec.cfpb.gov https://*.mapbox.com https://www.google-analytics.com https://s3.amazonaws.com;"; + add_header Content-Security-Policy "default-src 'self' blob:; script-src 'self' 'unsafe-inline' blob: data: https://tagmanager.google.com https://www.googletagmanager.com https://www.google-analytics.com https://*.cfpb.gov https://www.consumerfinance.gov; img-src 'self' blob: data: https://www.google-analytics.com https://raw.githubusercontent.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; frame-src 'self' https://www.youtube.com/ https://ffiec.cfpb.gov/; connect-src 'self' ws://*.cfpb.gov https://*.cfpb.gov https://www.consumerfinance.gov https://raw.githubusercontent.com https://ffiec-api.cfpb.gov https://ffiec.cfpb.gov https://*.mapbox.com https://www.google-analytics.com https://s3.amazonaws.com;"; # Restrict referrer add_header Referrer-Policy "strict-origin"; From 8b309731f60040cab1e63389ef7d346a0773529a Mon Sep 17 00:00:00 2001 From: Meissa Dia Date: Wed, 12 Aug 2020 14:53:09 -0600 Subject: [PATCH 02/20] Fetch websocket progress updates and store in redux --- src/filing/actions/listenForProgress.js | 148 ++++++++++++++++++ src/filing/actions/pollForProgress.js | 10 +- .../actions/receiveProcessingProgress.js | 10 ++ .../actions/requestProcessingProgress.js | 7 + src/filing/constants/index.js | 5 +- src/filing/reducers/index.js | 4 +- src/filing/reducers/processProgress.js | 19 +++ 7 files changed, 200 insertions(+), 3 deletions(-) create mode 100644 src/filing/actions/listenForProgress.js create mode 100644 src/filing/actions/receiveProcessingProgress.js create mode 100644 src/filing/actions/requestProcessingProgress.js create mode 100644 src/filing/reducers/processProgress.js diff --git a/src/filing/actions/listenForProgress.js b/src/filing/actions/listenForProgress.js new file mode 100644 index 000000000..13cc25fed --- /dev/null +++ b/src/filing/actions/listenForProgress.js @@ -0,0 +1,148 @@ +import fetchEdits from './fetchEdits.js' +import receiveSubmission from './receiveSubmission.js' +import receiveError from './receiveError.js' +import hasHttpError from './hasHttpError.js' +import { getLatestSubmission } from '../api/api.js' +import requestProcessingProgress from './requestProcessingProgress' +import receiveProcessingProgress from './receiveProcessingProgress' +import { error } from '../utils/log.js' +import { + SYNTACTICAL_VALIDITY_EDITS, + NO_MACRO_EDITS, + MACRO_EDITS, + UPLOADED +} from '../constants/statusCodes.js' + +// Extract completion percentage +export const parseProgress = string => { + if (!string.match(/^InProgress/)) return string + return string.match(/\d{1,}/)[0] +} + +/* Websocket Listener */ +export default function listenForProgress() { + return (dispatch) => { + if (!window.location.pathname.match('/upload')) + return Promise.resolve(null) + + return getLatestSubmission() + .then((json) => { + return hasHttpError(json).then((hasError) => { + if (hasError) { + dispatch(receiveError(json)) + throw new Error(json && `${json.status}: ${json.statusText}`) + } + return dispatch(receiveSubmission(json)) + }) + }) + .then((json) => { + if (!json) + return + + const { status, id } = json + const { lei, period, sequenceNumber } = id + const { year, quarter } = period + const { code } = status + + if (code >= UPLOADED) { + // Open a websocket and listen for updates + const wsBaseUrl = process.env.REACT_APP_ENVIRONMENT === 'CI' + ? `${window.location.hostname}:8080` + : `${window.location.host}/v2/filing` + + const wsProgressUrl = quarter + ? `/institutions/${lei}/filings/${year}/quarter/${quarter}/submissions/${sequenceNumber}/progress` + : `/institutions/${lei}/filings/${year}/submissions/${sequenceNumber}/progress` + + let socket = new WebSocket(`ws://${wsBaseUrl}${wsProgressUrl}`) + + socket.onopen = (event) => { + console.log('>>> Socket open! Listening for Progress...') + dispatch(requestProcessingProgress()) + } + + // Listen for messages + socket.onmessage = (event) => { + const data = event.data && JSON.parse(event.data)[1] + + if (!data) + return + + const uploadStatus = { + syntactical: parseProgress(data.syntactical), + quality: parseProgress(data.quality), + macro: parseProgress(data.macro), + done: '' + } + + // No Syntactical erros and all others Completed + uploadStatus.done = + !!uploadStatus.syntactical.match(/Error/) || + Object.keys(uploadStatus).every((key) => { + if (key === 'done') + return true + return uploadStatus[key].match(/^Completed/) + }) + + console.log('> Progress: ', uploadStatus) + + if (uploadStatus.done) { + console.log('<<< Closing Socket!') + socket.close(1000, 'Done Processing') + + // Push loader to 100% + // FIX: Shouldn't be needed if we're replacing the single loader bar + // with a multistage version which will be driven by the data + // returned from the Websocket + getLatestSubmission().then((json) => dispatch(receiveSubmission(json)) + ) + + // Save status updates + dispatch(receiveProcessingProgress({ status: uploadStatus })) + + const hasEdits = Object.keys(uploadStatus).some((key) => { + if (key === 'done') + return false + return uploadStatus[key].match(/Error/) + }) + + if (hasEdits) + return dispatch(fetchEdits()) + } + else { + dispatch(receiveProcessingProgress({ status: uploadStatus })) + } + } + + // TODO: Anything special on close? + socket.onclose = (event) => { + if (event.wasClean) { + console.log( + `[close] Connection closed cleanly, code=${event.code} reason=${event.reason}` + ) + } + else { + // e.g. server process killed or network down + // event.code is usually 1006 in this case + console.log('[close] Connection died') + } + } + + // TODO: What to do on websocket error? + socket.onerror = function (error) { + console.log(`[error] ${error.message}`) + } + } + else if ( + // only get edits when we've reached a terminal edit state + code === SYNTACTICAL_VALIDITY_EDITS || + code === NO_MACRO_EDITS || + code === MACRO_EDITS) { + return dispatch(fetchEdits()) + } + }) + .catch((err) => { + error(err) + }) + } +} diff --git a/src/filing/actions/pollForProgress.js b/src/filing/actions/pollForProgress.js index 183712644..6b9c6d84e 100644 --- a/src/filing/actions/pollForProgress.js +++ b/src/filing/actions/pollForProgress.js @@ -1,5 +1,6 @@ import fetchEdits from './fetchEdits.js' import receiveSubmission from './receiveSubmission.js' +import listenForProgress from './listenForProgress' import receiveError from './receiveError.js' import hasHttpError from './hasHttpError.js' import { getLatestSubmission } from '../api/api.js' @@ -9,7 +10,9 @@ import { SYNTACTICAL_VALIDITY_EDITS, NO_MACRO_EDITS, MACRO_EDITS, - VALIDATED + UPLOADED, + VALIDATED, + VALIDATING, } from '../constants/statusCodes.js' export const makeDurationGetter = () => { @@ -51,6 +54,11 @@ export default function pollForProgress() { .then(json => { if (!json) return const { code } = json.status + + // Switch to Websocket if file is uploaded and still processing + if(code >= UPLOADED && code <= VALIDATING) + return dispatch(listenForProgress()) + if ( // continue polling until we reach a status that isn't processing code !== PARSED_WITH_ERRORS && diff --git a/src/filing/actions/receiveProcessingProgress.js b/src/filing/actions/receiveProcessingProgress.js new file mode 100644 index 000000000..3cf2ff00d --- /dev/null +++ b/src/filing/actions/receiveProcessingProgress.js @@ -0,0 +1,10 @@ +import * as types from '../constants' + +export default function receiveProcessingProgress({ status }) { + return (dispatch) => { + return dispatch({ + type: types.RECEIVE_PROCESSING_PROGRESS, + status + }) + } +} \ No newline at end of file diff --git a/src/filing/actions/requestProcessingProgress.js b/src/filing/actions/requestProcessingProgress.js new file mode 100644 index 000000000..c705b6a2e --- /dev/null +++ b/src/filing/actions/requestProcessingProgress.js @@ -0,0 +1,7 @@ +import * as types from '../constants' + +export default function requestProcessingProgress() { + return { + type: types.REQUEST_PROCESSING_PROGRESS + } +} \ No newline at end of file diff --git a/src/filing/constants/index.js b/src/filing/constants/index.js index 39de4c9af..0c178fee6 100644 --- a/src/filing/constants/index.js +++ b/src/filing/constants/index.js @@ -82,4 +82,7 @@ export const RECEIVE_LATEST_SUBMISSION = 'RECEIVE_LATEST_SUBMISSION' export const RECEIVE_INSTITUTION_NOT_FOUND = 'RECEIVE_INSTITUTION_NOT_FOUND' -export const RECEIVE_FILING_PAGE = 'RECEIVE_FILING_PAGE' \ No newline at end of file +export const RECEIVE_FILING_PAGE = 'RECEIVE_FILING_PAGE' + +export const REQUEST_PROCESSING_PROGRESS = 'REQUEST_PROCESSING_PROGRESS' +export const RECEIVE_PROCESSING_PROGRESS = 'RECEIVE_PROCESSING_PROGRESS' \ No newline at end of file diff --git a/src/filing/reducers/index.js b/src/filing/reducers/index.js index 74deaec54..dde4e30c5 100644 --- a/src/filing/reducers/index.js +++ b/src/filing/reducers/index.js @@ -20,6 +20,7 @@ import redirecting from './redirecting.js' import latestSubmissions from './latestSubmissions' import refiling from './refiling' import filingPeriodOptions from './filingPeriodOptions' +import processProgress from './processProgress' export default combineReducers({ lei, @@ -41,5 +42,6 @@ export default combineReducers({ redirecting, latestSubmissions, refiling, - filingPeriodOptions + filingPeriodOptions, + processProgress }) diff --git a/src/filing/reducers/processProgress.js b/src/filing/reducers/processProgress.js new file mode 100644 index 000000000..4fe5d2592 --- /dev/null +++ b/src/filing/reducers/processProgress.js @@ -0,0 +1,19 @@ +import * as types from '../constants' + +const defaultState = { + syntactical: 'Waiting', + quality: 'Waiting', + macro: 'Waiting', + done: false +} + +export default (state = defaultState, action) => { + switch (action.type) { + case types.REQUEST_PROCESSING_PROGRESS: + return defaultState + case types.RECEIVE_PROCESSING_PROGRESS: + return action.status + default: + return state + } +} \ No newline at end of file From c451f6f81e00802ab042f9f1de349b12d9f82662 Mon Sep 17 00:00:00 2001 From: Meissa Dia Date: Thu, 13 Aug 2020 10:38:28 -0600 Subject: [PATCH 03/20] Add text based display of progress status --- src/filing/actions/listenForProgress.js | 19 +++++------ src/filing/reducers/processProgress.js | 4 +-- .../upload/FileProcessingProgress.jsx | 32 +++++++++++++++++++ src/filing/submission/upload/container.jsx | 9 ++++-- src/filing/submission/upload/index.jsx | 18 +++++++++-- 5 files changed, 64 insertions(+), 18 deletions(-) create mode 100644 src/filing/submission/upload/FileProcessingProgress.jsx diff --git a/src/filing/actions/listenForProgress.js b/src/filing/actions/listenForProgress.js index 13cc25fed..199ada727 100644 --- a/src/filing/actions/listenForProgress.js +++ b/src/filing/actions/listenForProgress.js @@ -19,6 +19,8 @@ export const parseProgress = string => { return string.match(/\d{1,}/)[0] } +const shouldSkipKey = key => ['done', 'fetched'].indexOf(key) > -1 + /* Websocket Listener */ export default function listenForProgress() { return (dispatch) => { @@ -65,22 +67,19 @@ export default function listenForProgress() { socket.onmessage = (event) => { const data = event.data && JSON.parse(event.data)[1] - if (!data) - return + if (!data) return const uploadStatus = { syntactical: parseProgress(data.syntactical), quality: parseProgress(data.quality), macro: parseProgress(data.macro), - done: '' } // No Syntactical erros and all others Completed uploadStatus.done = !!uploadStatus.syntactical.match(/Error/) || Object.keys(uploadStatus).every((key) => { - if (key === 'done') - return true + if (shouldSkipKey(key)) return true return uploadStatus[key].match(/^Completed/) }) @@ -94,20 +93,18 @@ export default function listenForProgress() { // FIX: Shouldn't be needed if we're replacing the single loader bar // with a multistage version which will be driven by the data // returned from the Websocket - getLatestSubmission().then((json) => dispatch(receiveSubmission(json)) - ) + // getLatestSubmission().then((json) => dispatch(receiveSubmission(json)) + // ) // Save status updates dispatch(receiveProcessingProgress({ status: uploadStatus })) const hasEdits = Object.keys(uploadStatus).some((key) => { - if (key === 'done') - return false + if (shouldSkipKey(key)) return false return uploadStatus[key].match(/Error/) }) - if (hasEdits) - return dispatch(fetchEdits()) + if (hasEdits) return dispatch(fetchEdits()) } else { dispatch(receiveProcessingProgress({ status: uploadStatus })) diff --git a/src/filing/reducers/processProgress.js b/src/filing/reducers/processProgress.js index 4fe5d2592..7124697e7 100644 --- a/src/filing/reducers/processProgress.js +++ b/src/filing/reducers/processProgress.js @@ -10,9 +10,9 @@ const defaultState = { export default (state = defaultState, action) => { switch (action.type) { case types.REQUEST_PROCESSING_PROGRESS: - return defaultState + return { ...defaultState, fetched: true } case types.RECEIVE_PROCESSING_PROGRESS: - return action.status + return { ...state, ...action.status } default: return state } diff --git a/src/filing/submission/upload/FileProcessingProgress.jsx b/src/filing/submission/upload/FileProcessingProgress.jsx new file mode 100644 index 000000000..1cafbf7bd --- /dev/null +++ b/src/filing/submission/upload/FileProcessingProgress.jsx @@ -0,0 +1,32 @@ +import React, { useEffect } from 'react'; +import { UPLOADING } from '../../constants/statusCodes' + +const parseProgress = str => { + const digits = str.match(/^\d/) && str + if(digits) return digits + '%' + if(str === 'Waiting') return str + if(str.match(/Error/)) return 'Errors' + return 'Done √' +} + +const FileProcessingProgress = ({ progress, uploading, code, watchProgress }) => { + const { done, syntactical, macro, quality, fetched } = progress + + useEffect(() => { + if (!fetched) watchProgress() + }, [fetched]) + + if (code < UPLOADING && !uploading) return null + + return ( +
+
Uploading{uploading ? '...in progress' : ': Done √'}
+
Syntactial: {parseProgress(syntactical)}
+
Quality: {parseProgress(quality)}
+
Macro: {parseProgress(macro)}
+ {done &&
Done!
} +
+ ) +} + +export default FileProcessingProgress diff --git a/src/filing/submission/upload/container.jsx b/src/filing/submission/upload/container.jsx index 0338a7101..9f1008d6d 100644 --- a/src/filing/submission/upload/container.jsx +++ b/src/filing/submission/upload/container.jsx @@ -2,9 +2,10 @@ import { connect } from 'react-redux' import Upload from './index.jsx' import handleFile from '../../actions/handleFile.js' import pollForProgress from '../../actions/pollForProgress.js' +import listenForProgress from '../../actions/listenForProgress.js' export function mapStateToProps(state) { - const { lei, filingPeriod, submission }= state.app + const { lei, filingPeriod, submission, processProgress }= state.app const code = submission.status.code const filename = submission.filename @@ -23,7 +24,8 @@ export function mapStateToProps(state) { filename, filingPeriod, lei, - uploading + uploading, + processProgress, } } @@ -35,6 +37,9 @@ export function mapDispatchToProps(dispatch) { }, pollSubmission() { dispatch(pollForProgress()) + }, + watchProgress() { + dispatch(listenForProgress()) } } } diff --git a/src/filing/submission/upload/index.jsx b/src/filing/submission/upload/index.jsx index 5e21557f4..8b2ed6709 100644 --- a/src/filing/submission/upload/index.jsx +++ b/src/filing/submission/upload/index.jsx @@ -2,6 +2,7 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import Alert from '../../../common/Alert.jsx' import ValidationProgress from './ValidationProgress.jsx' +import FileProcessingProgress from './FileProcessingProgress' import Dropzone from 'react-dropzone' import DropzoneContent from './DropzoneContent.jsx' import { @@ -50,7 +51,9 @@ export default class Upload extends Component { filename, filingPeriod, lei, - uploading + uploading, + processProgress, + watchProgress } = this.props return ( @@ -87,7 +90,16 @@ export default class Upload extends Component { ) }} - + {/* + /> */} ) } From 789aa655cf0fa9513650b860f98f9411680440a9 Mon Sep 17 00:00:00 2001 From: Meissa Dia Date: Fri, 14 Aug 2020 14:06:37 -0600 Subject: [PATCH 04/20] Add progress bar display of processing status --- src/filing/actions/fetchUpload.js | 2 + src/filing/actions/listenForProgress.js | 20 ++- .../upload/FileProcessingProgress.css | 32 ++++ .../upload/FileProcessingProgress.jsx | 24 +-- src/filing/submission/upload/ProgressBar.jsx | 43 ++++++ src/filing/submission/upload/UploadBar.jsx | 143 ++++++++++++++++++ .../submission/upload/ValidationProgress.css | 2 +- .../submission/upload/ValidationProgress.jsx | 7 +- src/filing/submission/upload/index.jsx | 7 +- 9 files changed, 257 insertions(+), 23 deletions(-) create mode 100644 src/filing/submission/upload/FileProcessingProgress.css create mode 100644 src/filing/submission/upload/ProgressBar.jsx create mode 100644 src/filing/submission/upload/UploadBar.jsx diff --git a/src/filing/actions/fetchUpload.js b/src/filing/actions/fetchUpload.js index 4196f5793..60ea6b96f 100644 --- a/src/filing/actions/fetchUpload.js +++ b/src/filing/actions/fetchUpload.js @@ -6,10 +6,12 @@ import receiveUpload from './receiveUpload.js' import hasHttpError from './hasHttpError.js' import receiveUploadError from './receiveUploadError.js' import { error } from '../utils/log.js' +import requestProcessingProgress from './requestProcessingProgress' export default function fetchUpload(file) { return dispatch => { dispatch(requestUpload()) + dispatch(requestProcessingProgress()) const data = new FormData() data.append('file', file) diff --git a/src/filing/actions/listenForProgress.js b/src/filing/actions/listenForProgress.js index 199ada727..01ec4b08c 100644 --- a/src/filing/actions/listenForProgress.js +++ b/src/filing/actions/listenForProgress.js @@ -56,7 +56,7 @@ export default function listenForProgress() { ? `/institutions/${lei}/filings/${year}/quarter/${quarter}/submissions/${sequenceNumber}/progress` : `/institutions/${lei}/filings/${year}/submissions/${sequenceNumber}/progress` - let socket = new WebSocket(`ws://${wsBaseUrl}${wsProgressUrl}`) + let socket = new WebSocket(`wss://${wsBaseUrl}${wsProgressUrl}`) socket.onopen = (event) => { console.log('>>> Socket open! Listening for Progress...') @@ -85,17 +85,21 @@ export default function listenForProgress() { console.log('> Progress: ', uploadStatus) + // Update Submission for status messaging + getLatestSubmission().then((json) => { + return hasHttpError(json).then((hasError) => { + if (hasError) { + dispatch(receiveError(json)) + throw new Error(json && `${json.status}: ${json.statusText}`) + } + return dispatch(receiveSubmission(json)) + }) + }) + if (uploadStatus.done) { console.log('<<< Closing Socket!') socket.close(1000, 'Done Processing') - // Push loader to 100% - // FIX: Shouldn't be needed if we're replacing the single loader bar - // with a multistage version which will be driven by the data - // returned from the Websocket - // getLatestSubmission().then((json) => dispatch(receiveSubmission(json)) - // ) - // Save status updates dispatch(receiveProcessingProgress({ status: uploadStatus })) diff --git a/src/filing/submission/upload/FileProcessingProgress.css b/src/filing/submission/upload/FileProcessingProgress.css new file mode 100644 index 000000000..a1b586567 --- /dev/null +++ b/src/filing/submission/upload/FileProcessingProgress.css @@ -0,0 +1,32 @@ +#fileProcessProgress { + margin: 2rem 0 0 0; +} + +.barLabel { + margin: .5rem 0; + font-weight: bold; +} + +.progressBar { + height: 25px; + width: 100%; + background-color: #e0e0de; + border-radius: 10px; + margin: 1rem 0; +} + +.progressBar .fill { + line-height: 25px; + height: 100%; + width: 1%; + background-color: grey; + border-radius: inherit; + text-align: right; + transition: width 1s ease-in-out; +} + +.progressBar .label { + padding: 5px; + color: white; + font-weight: bold; +} diff --git a/src/filing/submission/upload/FileProcessingProgress.jsx b/src/filing/submission/upload/FileProcessingProgress.jsx index 1cafbf7bd..e564c0618 100644 --- a/src/filing/submission/upload/FileProcessingProgress.jsx +++ b/src/filing/submission/upload/FileProcessingProgress.jsx @@ -1,5 +1,8 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { UPLOADING } from '../../constants/statusCodes' +import { UploadBar } from './UploadBar' +import { ProgressBar } from './ProgressBar' +import './FileProcessingProgress.css' const parseProgress = str => { const digits = str.match(/^\d/) && str @@ -9,22 +12,25 @@ const parseProgress = str => { return 'Done √' } -const FileProcessingProgress = ({ progress, uploading, code, watchProgress }) => { +const hasError = str => str.match(/Err/) + +const FileProcessingProgress = ({ progress, uploading, code, watchProgress, filingPeriod, lei }) => { const { done, syntactical, macro, quality, fetched } = progress useEffect(() => { if (!fetched) watchProgress() - }, [fetched]) + }, [fetched, watchProgress]) if (code < UPLOADING && !uploading) return null + + const hasSynEdits = hasError(syntactical) return ( -
-
Uploading{uploading ? '...in progress' : ': Done √'}
-
Syntactial: {parseProgress(syntactical)}
-
Quality: {parseProgress(quality)}
-
Macro: {parseProgress(macro)}
- {done &&
Done!
} +
+ + + +
) } diff --git a/src/filing/submission/upload/ProgressBar.jsx b/src/filing/submission/upload/ProgressBar.jsx new file mode 100644 index 000000000..00acab765 --- /dev/null +++ b/src/filing/submission/upload/ProgressBar.jsx @@ -0,0 +1,43 @@ +import React from 'react' + +const YELLOW = '#dc731c' +const BLUE = '#0071bc' +const RED = '#e31c3d' + +const calcPercent = pct => { + if(typeof pct === 'number') return pct + if(pct.match(/^\d/)) return parseInt(pct) + if(pct.match(/^Done|^Errors/)) return 100 + return 0 +} + +export const ProgressBar = ({ percent, label, error, hasPrevError }) => { + if([null, undefined].indexOf(percent) > -1) return null + let pct = calcPercent(percent) + + const fillStyles = { + width: `${pct}%`, + backgroundColor: pct === 100 ? BLUE : YELLOW + } + if (error) fillStyles.backgroundColor = RED + + const progressBarStyles = {} + if(hasPrevError) progressBarStyles.backgroundColor = YELLOW + + return ( + <> +
{label}
+
+
+ {`${pct}%`} +
+
+ + ) +} + +ProgressBar.defaultProps = { + percent: '0', + error: false, + waiting: false +} diff --git a/src/filing/submission/upload/UploadBar.jsx b/src/filing/submission/upload/UploadBar.jsx new file mode 100644 index 000000000..9f3735099 --- /dev/null +++ b/src/filing/submission/upload/UploadBar.jsx @@ -0,0 +1,143 @@ +import React, { Component } from 'react' +import { ProgressBar } from "./ProgressBar" + +export class UploadBar extends Component { + constructor(props) { + super(props) + this.SCALING_FACTOR = 1 + if (props.file) { + this.SCALING_FACTOR = props.file.size / 1e6 + if (this.SCALING_FACTOR < 1) this.SCALING_FACTOR = 1 + if (this.SCALING_FACTOR > 5) this.SCALING_FACTOR = 5 + } + const fillWidth = this.getSavedWidth(props.filingPeriod, props.lei) || 1 + this.state = { fillWidth, firstRender: true } + this.setState = this.setState.bind(this) + } + + componentWillUnmount() { + clearTimeout(this.timeout) + this.timeout = null + } + + componentDidMount() { + if (this.state.firstRender) { + const fillWidth = this.getSavedWidth(this.props.filingPeriod, this.props.lei) + this.setState({ firstRender: false, fillWidth }) + } + } + + saveWidth(filingPeriod, lei, width) { + // if (this.props.errorUpload || this.props.errorApp) width = 0 + localStorage.setItem(`HMDA_UPLOAD_PROGRESS/${filingPeriod}/${lei}`, JSON.stringify(width)) + } + + getSavedWidth(filingPeriod, lei) { + return lei ? JSON.parse(localStorage.getItem(`HMDA_UPLOAD_PROGRESS/${filingPeriod}/${lei}`)) : 0 + } + + getNextWidth() { + const fillWidth = this.state.fillWidth + this.timeout = setTimeout( + this.setNextWidth(fillWidth), + this.SCALING_FACTOR * 200 * Math.pow(2, 50 / (100 - fillWidth)) + ) + } + + setNextWidth(currWidth) { + return () => { + this.timeout = null + let nextWidth = parseInt(currWidth) + 1 + if (nextWidth > 100) nextWidth = '100' + this.saveWidth(this.props.filingPeriod, this.props.lei, nextWidth) + this.setState({ fillWidth: nextWidth }) + } + } + + getFillWidth() { + if (this.state.firstRender) + return this.getSavedWidth(this.props.filingPeriod, this.props.lei) + + if (parseInt(this.state.fillWidth) > 100 || !this.props.uploading) { + this.saveWidth(this.props.filingPeriod, this.props.lei, 0) + return '100' + } + + if (!this.timeout) this.getNextWidth() + + return this.state.fillWidth + } + + render() { + return + } +} + + + + + + + + + + + + + + + + + + + + + + +// const UploadBar = props => { +// const [state, updateState] = useState({ fillWidth: '10', firstRender: true, scalingFactor: 1 }) + +// const setState = obj => updateState(state => ({ ...state, ...obj })) + +// useEffect(() => { +// if(state.firstRender) setState({ firstRender: false }) +// }, [state.firstRender]) + +// useEffect(() => { +// return function onUnmount() { +// console.log('UploadBar will unmount') +// } +// }, []) + +// function getNextWidth() { +// const fillWidth = state.fillWidth +// setState({ timeout: setTimeout( +// setNextWidth(fillWidth), +// state.SCALING_FACTOR * 200 * Math.pow(2, 50 / (100 - fillWidth)) +// )}) +// } + +// function setNextWidth(currWidth) { +// return () => { +// state.timeout = null +// let nextWidth = parseInt(currWidth) + 1 +// if (nextWidth > 100) nextWidth = '100' +// setState({ fillWidth: nextWidth.toString() }) +// } +// } + +// function getFillWidth() { +// if(state.firstRender) return '0' +// if (parseInt(state.fillWidth) > 100) return '100' +// if (!props.uploading) return '100' +// else if (!state.timeout) getNextWidth() + +// return state.fillWidth +// } + + +// console.log('First render: ', state.firstRender) +// console.log('fillWidth: ', state.fillWidth) + +// return +// } \ No newline at end of file diff --git a/src/filing/submission/upload/ValidationProgress.css b/src/filing/submission/upload/ValidationProgress.css index d585fdbea..045f2c480 100644 --- a/src/filing/submission/upload/ValidationProgress.css +++ b/src/filing/submission/upload/ValidationProgress.css @@ -14,7 +14,7 @@ } .ValidationProgress { - padding-top: 1em; + padding-top: 0rem; position: relative; } .ValidationProgress .usa-button { diff --git a/src/filing/submission/upload/ValidationProgress.jsx b/src/filing/submission/upload/ValidationProgress.jsx index 2b2ca6bfd..c6ba46f94 100644 --- a/src/filing/submission/upload/ValidationProgress.jsx +++ b/src/filing/submission/upload/ValidationProgress.jsx @@ -101,13 +101,14 @@ export default class ValidationProgress extends PureComponent { if (code < UPLOADING && !uploading) return null return (
+ {/* TODO: Cleanup unused styles */} {/* the background bar */} -
+ {/*
*/} {/* the progress bar */} -
+ /> */} - {/* */} + />
) } From 9b68b360974805165bde2c9e8db8bd8df9f130da Mon Sep 17 00:00:00 2001 From: Meissa Dia Date: Thu, 27 Aug 2020 22:58:18 -0600 Subject: [PATCH 05/20] Create multistage progress bar --- .../upload/ProgressBar/ProgressBar.css | 117 ++++++++++++++++++ .../ProgressBar/StackedProgressBars.css | 35 ++++++ .../ProgressBar/StackedProgressBars.jsx | 8 ++ .../upload/ProgressBar/constants.js | 18 +++ .../submission/upload/ProgressBar/index.jsx | 60 +++++++++ 5 files changed, 238 insertions(+) create mode 100644 src/filing/submission/upload/ProgressBar/ProgressBar.css create mode 100644 src/filing/submission/upload/ProgressBar/StackedProgressBars.css create mode 100644 src/filing/submission/upload/ProgressBar/StackedProgressBars.jsx create mode 100644 src/filing/submission/upload/ProgressBar/constants.js create mode 100644 src/filing/submission/upload/ProgressBar/index.jsx diff --git a/src/filing/submission/upload/ProgressBar/ProgressBar.css b/src/filing/submission/upload/ProgressBar/ProgressBar.css new file mode 100644 index 000000000..83ed36050 --- /dev/null +++ b/src/filing/submission/upload/ProgressBar/ProgressBar.css @@ -0,0 +1,117 @@ +/* Container */ +.meter { + height: 26px; + position: relative; + background: #555; + -moz-border-radius: 25px; + -webkit-border-radius: 25px; + border-radius: 25px; + box-shadow: inset 0 -1px 1px rgba(255, 255, 255, 0.3); + border: 1px solid black; +} + +/* Fill */ +.meter > .fill { + display: block; + height: 100%; + border-top-right-radius: 20px; + border-bottom-right-radius: 20px; + border-top-left-radius: 20px; + border-bottom-left-radius: 20px; + background-color: rgb(43, 194, 83); + background-image: linear-gradient( + center bottom, + rgb(43, 194, 83) 37%, + rgb(84, 240, 84) 69% + ); + box-shadow: inset 0 2px 9px rgba(255, 255, 255, 0.3), + inset 0 -2px 6px rgba(0, 0, 0, 0.4); + position: relative; + overflow: visible; + transition: width 0.5s, background-image .5s; +} + +/* Fill Statuses */ +.meter.pending, .meter.skipped { background: #80807e; } +.meter.pending .fill, .meter.skipped .fill { background: #555; } + +.meter.done { background: #bee2fa; } +.meter.done .fill { background: #0071bc; } + +.meter.error { background: #f9c0c0; } +.meter.error .fill { background: #e31c3d; } + +.meter.warn { background: #dc731c; } +.meter.warn .fill { background: #dc731c; } + +.meter.progress { background: #75afe9; } +.meter.progress .fill { background: #308ae4; } + +/* Stripped Overlay */ +.meter.striped > .fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background-image: linear-gradient( + -45deg, + rgba(255, 255, 255, 0.2) 25%, + transparent 25%, + transparent 50%, + rgba(255, 255, 255, 0.2) 50%, + rgba(255, 255, 255, 0.2) 75%, + transparent 75%, + transparent + ); + z-index: 1; + background-size: 50px 50px; + border-top-right-radius: 25px; + border-bottom-right-radius: 25px; + border-top-left-radius: 25px; + border-bottom-left-radius: 25px; + overflow: visible; +} + +/* Step label (over bar) */ +.meter .step-label { + position: absolute; + left: 0; + right: 0; + top: -3rem; + bottom: 0; + z-index: 99; + font-weight: bold; + overflow: visible; + padding: 3px 0; + background: transparent; + text-align: center; +} + +/* Progress label (within bar) */ +.meter .fill .status-label { + text-align: center; + width: 100%; + height: 100%; + position: absolute; + padding-top: 2px; + color: white; +} + +/* Animate Stripes */ +.meter.animate > .fill::after, +.meter.animate > .fill > span { + animation: barberPole 2s linear infinite; + text-align: center; +} + +/* Keyframes */ +@keyframes barberPole { + 0% { + background-position: 0 0; + } + 100% { + background-position: 50px 50px; + } +} diff --git a/src/filing/submission/upload/ProgressBar/StackedProgressBars.css b/src/filing/submission/upload/ProgressBar/StackedProgressBars.css new file mode 100644 index 000000000..a8d5a5f53 --- /dev/null +++ b/src/filing/submission/upload/ProgressBar/StackedProgressBars.css @@ -0,0 +1,35 @@ +/* Stacked */ +.stacked-progress-bars { + display: flex; + flex-flow: row nowrap; + width: 100%; + justify-content: center; + align-items: center; +} + +.stacked-progress-bars .meter { + width: 100%; + margin: 0; + margin-top: 2rem; +} + +.stacked-progress-bars .meter, +.stacked-progress-bars .fill, +.stacked-progress-bars .meter > .fill::after { + border-radius: 0; + box-shadow: none; +} + +.stacked-progress-bars .meter:first-child, +.stacked-progress-bars .meter:first-child .fill, +.stacked-progress-bars .meter:first-child > .fill::after { + border-top-left-radius: 25px; + border-bottom-left-radius: 25px; +} + +.stacked-progress-bars .meter:last-child, +.stacked-progress-bars .meter:last-child .fill, +.stacked-progress-bars .meter:last-child > .fill::after { + border-top-right-radius: 25px; + border-bottom-right-radius: 25px; +} \ No newline at end of file diff --git a/src/filing/submission/upload/ProgressBar/StackedProgressBars.jsx b/src/filing/submission/upload/ProgressBar/StackedProgressBars.jsx new file mode 100644 index 000000000..3aa5895de --- /dev/null +++ b/src/filing/submission/upload/ProgressBar/StackedProgressBars.jsx @@ -0,0 +1,8 @@ +import React from 'react' +import './StackedProgressBars.css' + +const StackedProgressBars = (props) => ( +
{props.children}
+) + +export default StackedProgressBars diff --git a/src/filing/submission/upload/ProgressBar/constants.js b/src/filing/submission/upload/ProgressBar/constants.js new file mode 100644 index 000000000..d803ce997 --- /dev/null +++ b/src/filing/submission/upload/ProgressBar/constants.js @@ -0,0 +1,18 @@ +export const STATUS = { + PENDING: 0, + PROGRESS: 1, + DONE: 2, + EDITS: 3, + ERROR: 4, + SKIP: 5, +} + +export const CHECK = { + isPending: (status) => status === STATUS.PENDING, + isProgress: (status) => status === STATUS.PROGRESS, + isDone: (status) => status === STATUS.DONE, + isEdits: (status) => status === STATUS.EDITS, + isError: (status) => status === STATUS.ERROR, + isSkip: (status) => status === STATUS.SKIP, +} + diff --git a/src/filing/submission/upload/ProgressBar/index.jsx b/src/filing/submission/upload/ProgressBar/index.jsx new file mode 100644 index 000000000..fd7d178c8 --- /dev/null +++ b/src/filing/submission/upload/ProgressBar/index.jsx @@ -0,0 +1,60 @@ +import React from 'react' +import { STATUS, CHECK } from './constants' +import './ProgressBar.css' + +const ProgressBar = (props) => { + const { label } = props + + return ( +
+ {label} + + {fillLabel(props)} + +
+ ) +} + +const containerClass = ({ status }) => { + let cname = 'meter' + if (CHECK.isPending(status)) return cname + ' pending' + if (CHECK.isProgress(status)) return cname + ' progress animate striped' + if (CHECK.isDone(status)) return cname + ' done' + if (CHECK.isError(status)) return cname + ' error' + if (CHECK.isEdits(status)) return cname + ' warn' + if (CHECK.isSkip(status)) return cname + ' skipped' + return cname +} + +const fillStyles = ({ pct, minWidth, maxWidth, status, label }) => { + let status100 = [STATUS.PENDING, STATUS.DONE, STATUS.EDITS, STATUS.ERROR] + let pctAdjusted + + if (status100.indexOf(status) > -1) pctAdjusted = 100 + else pctAdjusted = pct > maxWidth ? maxWidth : pct < minWidth ? minWidth : pct + + label === "Macro" && console.log(`${label} ${status} ${pctAdjusted}%`) + return { width: `${pctAdjusted}%` } +} + + +const fillLabel = ({ status, pct, isSV }) => { + if (CHECK.isProgress(status)) return `${pct}%` + if (CHECK.isError(status) && isSV) return 'Corrections Required' + if (CHECK.isError(status)) return 'Error' + if (CHECK.isEdits(status)) return 'Edits Found' + if (CHECK.isPending(status)) return 'Pending' + if (CHECK.isSkip(status)) return '' + return 'Complete' +} + +ProgressBar.defaultProps = { + animate: false, + isSV: false, + maxWidth: 100, + minWidth: 0, + pct: 45, + status: null, +} + +export default ProgressBar From bc5469619794ed5f80d5f7d24d0c64c0b0936066 Mon Sep 17 00:00:00 2001 From: Meissa Dia Date: Thu, 27 Aug 2020 22:59:49 -0600 Subject: [PATCH 06/20] Integrate new ProgressBar and StackedProgressBars --- src/filing/actions/listenForProgress.js | 2 +- .../upload/FileProcessingProgress.jsx | 50 +++++++++++++++---- .../submission/upload/ProgressBar/index.jsx | 1 - src/filing/submission/upload/UploadBar.jsx | 25 ++++++++-- src/filing/submission/upload/index.jsx | 3 -- 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/src/filing/actions/listenForProgress.js b/src/filing/actions/listenForProgress.js index 01ec4b08c..b65d1f91d 100644 --- a/src/filing/actions/listenForProgress.js +++ b/src/filing/actions/listenForProgress.js @@ -56,7 +56,7 @@ export default function listenForProgress() { ? `/institutions/${lei}/filings/${year}/quarter/${quarter}/submissions/${sequenceNumber}/progress` : `/institutions/${lei}/filings/${year}/submissions/${sequenceNumber}/progress` - let socket = new WebSocket(`wss://${wsBaseUrl}${wsProgressUrl}`) + let socket = new WebSocket(`ws://${wsBaseUrl}${wsProgressUrl}`) socket.onopen = (event) => { console.log('>>> Socket open! Listening for Progress...') diff --git a/src/filing/submission/upload/FileProcessingProgress.jsx b/src/filing/submission/upload/FileProcessingProgress.jsx index e564c0618..cd3b2d7b1 100644 --- a/src/filing/submission/upload/FileProcessingProgress.jsx +++ b/src/filing/submission/upload/FileProcessingProgress.jsx @@ -1,15 +1,25 @@ import React, { useEffect, useState } from 'react'; import { UPLOADING } from '../../constants/statusCodes' import { UploadBar } from './UploadBar' -import { ProgressBar } from './ProgressBar' +import ProgressBar from './ProgressBar/' +import StackedProgressBars from './ProgressBar/StackedProgressBars' +import { STATUS } from './ProgressBar/constants' import './FileProcessingProgress.css' -const parseProgress = str => { +const parsePercent = str => { const digits = str.match(/^\d/) && str - if(digits) return digits + '%' - if(str === 'Waiting') return str - if(str.match(/Error/)) return 'Errors' - return 'Done √' + if(digits) return digits + return '100' +} + +const getStatus = (str, prevErrors, isSV) => { + if (prevErrors) return STATUS.SKIP + if (str === 'Waiting' || prevErrors) return STATUS.PENDING + if (str === 'Completed') return STATUS.DONE + if (str === 'CompletedWithErrors' && !isSV) return STATUS.EDITS + if (hasError(str)) return STATUS.ERROR + + return STATUS.PROGRESS } const hasError = str => str.match(/Err/) @@ -27,10 +37,30 @@ const FileProcessingProgress = ({ progress, uploading, code, watchProgress, fili return (
- - - - + + + + + +
) } diff --git a/src/filing/submission/upload/ProgressBar/index.jsx b/src/filing/submission/upload/ProgressBar/index.jsx index fd7d178c8..7f5a83683 100644 --- a/src/filing/submission/upload/ProgressBar/index.jsx +++ b/src/filing/submission/upload/ProgressBar/index.jsx @@ -33,7 +33,6 @@ const fillStyles = ({ pct, minWidth, maxWidth, status, label }) => { if (status100.indexOf(status) > -1) pctAdjusted = 100 else pctAdjusted = pct > maxWidth ? maxWidth : pct < minWidth ? minWidth : pct - label === "Macro" && console.log(`${label} ${status} ${pctAdjusted}%`) return { width: `${pctAdjusted}%` } } diff --git a/src/filing/submission/upload/UploadBar.jsx b/src/filing/submission/upload/UploadBar.jsx index 9f3735099..f59c41768 100644 --- a/src/filing/submission/upload/UploadBar.jsx +++ b/src/filing/submission/upload/UploadBar.jsx @@ -1,6 +1,8 @@ import React, { Component } from 'react' -import { ProgressBar } from "./ProgressBar" +import ProgressBar from "./ProgressBar/" +import { STATUS } from './ProgressBar/constants' +const MIN_PCT = 10 export class UploadBar extends Component { constructor(props) { super(props) @@ -10,7 +12,7 @@ export class UploadBar extends Component { if (this.SCALING_FACTOR < 1) this.SCALING_FACTOR = 1 if (this.SCALING_FACTOR > 5) this.SCALING_FACTOR = 5 } - const fillWidth = this.getSavedWidth(props.filingPeriod, props.lei) || 1 + const fillWidth = this.getSavedWidth(props.filingPeriod, props.lei) || MIN_PCT this.state = { fillWidth, firstRender: true } this.setState = this.setState.bind(this) } @@ -33,14 +35,14 @@ export class UploadBar extends Component { } getSavedWidth(filingPeriod, lei) { - return lei ? JSON.parse(localStorage.getItem(`HMDA_UPLOAD_PROGRESS/${filingPeriod}/${lei}`)) : 0 + return lei ? JSON.parse(localStorage.getItem(`HMDA_UPLOAD_PROGRESS/${filingPeriod}/${lei}`)) : MIN_PCT } getNextWidth() { const fillWidth = this.state.fillWidth this.timeout = setTimeout( this.setNextWidth(fillWidth), - this.SCALING_FACTOR * 200 * Math.pow(2, 50 / (100 - fillWidth)) + this.SCALING_FACTOR * 75 * Math.pow(2, 50 / (100 - fillWidth)) ) } @@ -49,6 +51,7 @@ export class UploadBar extends Component { this.timeout = null let nextWidth = parseInt(currWidth) + 1 if (nextWidth > 100) nextWidth = '100' + if (nextWidth < MIN_PCT) nextWidth = `${MIN_PCT}` this.saveWidth(this.props.filingPeriod, this.props.lei, nextWidth) this.setState({ fillWidth: nextWidth }) } @@ -68,8 +71,19 @@ export class UploadBar extends Component { return this.state.fillWidth } + status() { + if(this.props.uploading) return STATUS.PROGRESS + return STATUS.DONE + } + render() { - return + return ( + + ) } } @@ -92,6 +106,7 @@ export class UploadBar extends Component { + // const UploadBar = props => { diff --git a/src/filing/submission/upload/index.jsx b/src/filing/submission/upload/index.jsx index 6b4f4c0c2..8bb798419 100644 --- a/src/filing/submission/upload/index.jsx +++ b/src/filing/submission/upload/index.jsx @@ -90,9 +90,6 @@ export default class Upload extends Component { ) }} - {/* - TODO: Replace ValidationProgress with a multistage version, driven by Websocket data - */} Date: Tue, 19 Oct 2021 20:30:46 -0600 Subject: [PATCH 07/20] Redux - Enable trace in dev tools --- src/filing/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/filing/index.js b/src/filing/index.js index c64e82553..8a85b8580 100644 --- a/src/filing/index.js +++ b/src/filing/index.js @@ -24,11 +24,13 @@ if (process.env.NODE_ENV !== 'production') { // use redux dev tools, extension required // see https://github.com/zalmoxisus/redux-devtools-extension#installation const { composeWithDevTools } = require('redux-devtools-extension') + const composeEnhancers = composeWithDevTools({trace: true, traceLimit: 25 }) + store = createStore( combineReducers({ app: appReducer }), - composeWithDevTools(applyMiddleware(...middleware)) + composeEnhancers(applyMiddleware(...middleware)) ) } else { store = createStore( From e1dfb378d1ce86880a1896c807dd1795b214b2f0 Mon Sep 17 00:00:00 2001 From: meissadia Date: Fri, 19 Nov 2021 09:51:55 -0700 Subject: [PATCH 08/20] Direct users to UPLOAD if file is processing --- src/filing/actions/listenForProgress.js | 5 ++--- src/filing/institutions/ViewButton.jsx | 2 +- src/filing/submission/upload/ProgressBar/ProgressBar.css | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/filing/actions/listenForProgress.js b/src/filing/actions/listenForProgress.js index b65d1f91d..b37e5a47e 100644 --- a/src/filing/actions/listenForProgress.js +++ b/src/filing/actions/listenForProgress.js @@ -38,8 +38,7 @@ export default function listenForProgress() { }) }) .then((json) => { - if (!json) - return + if (!json) return const { status, id } = json const { lei, period, sequenceNumber } = id @@ -75,7 +74,7 @@ export default function listenForProgress() { macro: parseProgress(data.macro), } - // No Syntactical erros and all others Completed + // No Syntactical errors and all others Completed uploadStatus.done = !!uploadStatus.syntactical.match(/Error/) || Object.keys(uploadStatus).every((key) => { diff --git a/src/filing/institutions/ViewButton.jsx b/src/filing/institutions/ViewButton.jsx index 2a29ed81c..cd02a26c0 100644 --- a/src/filing/institutions/ViewButton.jsx +++ b/src/filing/institutions/ViewButton.jsx @@ -30,7 +30,7 @@ const InstitutionViewButton = ({ submission, institution, filingPeriod, isClosed text = 'View upload progress' } else if (code === PARSED_WITH_ERRORS) { text = 'Review formatting errors' - } else if (code < NO_SYNTACTICAL_VALIDITY_EDITS) { + } else if (code < NO_MACRO_EDITS) { text = 'View progress' } else if (code > VALIDATING && code < VALIDATED && code !== NO_MACRO_EDITS) { text = 'Review edits' diff --git a/src/filing/submission/upload/ProgressBar/ProgressBar.css b/src/filing/submission/upload/ProgressBar/ProgressBar.css index 83ed36050..5f7f2ca66 100644 --- a/src/filing/submission/upload/ProgressBar/ProgressBar.css +++ b/src/filing/submission/upload/ProgressBar/ProgressBar.css @@ -35,13 +35,13 @@ .meter.pending, .meter.skipped { background: #80807e; } .meter.pending .fill, .meter.skipped .fill { background: #555; } -.meter.done { background: #bee2fa; } +.meter.done, .meter.done .fill { background: #0071bc; } .meter.error { background: #f9c0c0; } .meter.error .fill { background: #e31c3d; } -.meter.warn { background: #dc731c; } +.meter.warn, .meter.warn .fill { background: #dc731c; } .meter.progress { background: #75afe9; } From 463ea7522dd6d8b22933fa61899532f808e450ba Mon Sep 17 00:00:00 2001 From: meissadia Date: Fri, 19 Nov 2021 10:17:21 -0700 Subject: [PATCH 09/20] Cleanup unused code --- .../upload/FileProcessingProgress.jsx | 4 +- .../upload/ProgressBar/ProgressBar.css | 2 +- src/filing/submission/upload/UploadBar.jsx | 73 +------------------ .../submission/upload/ValidationProgress.jsx | 8 -- src/filing/submission/upload/index.jsx | 2 +- 5 files changed, 5 insertions(+), 84 deletions(-) diff --git a/src/filing/submission/upload/FileProcessingProgress.jsx b/src/filing/submission/upload/FileProcessingProgress.jsx index cd3b2d7b1..744a018a2 100644 --- a/src/filing/submission/upload/FileProcessingProgress.jsx +++ b/src/filing/submission/upload/FileProcessingProgress.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; import { UPLOADING } from '../../constants/statusCodes' import { UploadBar } from './UploadBar' import ProgressBar from './ProgressBar/' @@ -25,7 +25,7 @@ const getStatus = (str, prevErrors, isSV) => { const hasError = str => str.match(/Err/) const FileProcessingProgress = ({ progress, uploading, code, watchProgress, filingPeriod, lei }) => { - const { done, syntactical, macro, quality, fetched } = progress + const { syntactical, macro, quality, fetched } = progress useEffect(() => { if (!fetched) watchProgress() diff --git a/src/filing/submission/upload/ProgressBar/ProgressBar.css b/src/filing/submission/upload/ProgressBar/ProgressBar.css index 5f7f2ca66..65e50339a 100644 --- a/src/filing/submission/upload/ProgressBar/ProgressBar.css +++ b/src/filing/submission/upload/ProgressBar/ProgressBar.css @@ -47,7 +47,7 @@ .meter.progress { background: #75afe9; } .meter.progress .fill { background: #308ae4; } -/* Stripped Overlay */ +/* Striped Overlay */ .meter.striped > .fill::after { content: ''; position: absolute; diff --git a/src/filing/submission/upload/UploadBar.jsx b/src/filing/submission/upload/UploadBar.jsx index f59c41768..1643551ed 100644 --- a/src/filing/submission/upload/UploadBar.jsx +++ b/src/filing/submission/upload/UploadBar.jsx @@ -30,7 +30,6 @@ export class UploadBar extends Component { } saveWidth(filingPeriod, lei, width) { - // if (this.props.errorUpload || this.props.errorApp) width = 0 localStorage.setItem(`HMDA_UPLOAD_PROGRESS/${filingPeriod}/${lei}`, JSON.stringify(width)) } @@ -85,74 +84,4 @@ export class UploadBar extends Component { /> ) } -} - - - - - - - - - - - - - - - - - - - - - - - -// const UploadBar = props => { -// const [state, updateState] = useState({ fillWidth: '10', firstRender: true, scalingFactor: 1 }) - -// const setState = obj => updateState(state => ({ ...state, ...obj })) - -// useEffect(() => { -// if(state.firstRender) setState({ firstRender: false }) -// }, [state.firstRender]) - -// useEffect(() => { -// return function onUnmount() { -// console.log('UploadBar will unmount') -// } -// }, []) - -// function getNextWidth() { -// const fillWidth = state.fillWidth -// setState({ timeout: setTimeout( -// setNextWidth(fillWidth), -// state.SCALING_FACTOR * 200 * Math.pow(2, 50 / (100 - fillWidth)) -// )}) -// } - -// function setNextWidth(currWidth) { -// return () => { -// state.timeout = null -// let nextWidth = parseInt(currWidth) + 1 -// if (nextWidth > 100) nextWidth = '100' -// setState({ fillWidth: nextWidth.toString() }) -// } -// } - -// function getFillWidth() { -// if(state.firstRender) return '0' -// if (parseInt(state.fillWidth) > 100) return '100' -// if (!props.uploading) return '100' -// else if (!state.timeout) getNextWidth() - -// return state.fillWidth -// } - - -// console.log('First render: ', state.firstRender) -// console.log('fillWidth: ', state.fillWidth) - -// return -// } \ No newline at end of file +} \ No newline at end of file diff --git a/src/filing/submission/upload/ValidationProgress.jsx b/src/filing/submission/upload/ValidationProgress.jsx index c6ba46f94..6b13c75cd 100644 --- a/src/filing/submission/upload/ValidationProgress.jsx +++ b/src/filing/submission/upload/ValidationProgress.jsx @@ -101,14 +101,6 @@ export default class ValidationProgress extends PureComponent { if (code < UPLOADING && !uploading) return null return (
- {/* TODO: Cleanup unused styles */} - {/* the background bar */} - {/*
*/} - {/* the progress bar */} - {/*
*/} + />
) } From 60527df3dd19afe1f311b9a8e9dda8244cb36f07 Mon Sep 17 00:00:00 2001 From: Meis Date: Wed, 16 Mar 2022 16:05:04 -0600 Subject: [PATCH 10/20] [Fixtures] 2021 Submission file with zero edits --- cypress/fixtures/2021-FRONTENDTESTBANK9999-no-edits.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 cypress/fixtures/2021-FRONTENDTESTBANK9999-no-edits.txt diff --git a/cypress/fixtures/2021-FRONTENDTESTBANK9999-no-edits.txt b/cypress/fixtures/2021-FRONTENDTESTBANK9999-no-edits.txt new file mode 100644 index 000000000..198d64669 --- /dev/null +++ b/cypress/fixtures/2021-FRONTENDTESTBANK9999-no-edits.txt @@ -0,0 +1,2 @@ +1|FRONTENDTESTBANK9999|2021|4|Mr. Smug Pockets|555-555-5555|pockets@ficus.com|1234 Hocus Potato Way|Tatertown|UT|84096|9|1|53-1111111|FRONTENDTESTBANK9999 +2|FRONTENDTESTBANK9999|FRONTENDTESTBANK9999JAJZMZSDXF8A57HP1HJZQOZ66|20200613|3|2|2|2|3|218910|1|20210213|1234 Hocus Potato Way|Tatertown|NM|14755|35003|35003976400|1||||||4||||||2|3|1||||||||8||||||||1|4|2|4|2|3|75|8888|100|0|NA|3|2|8888|8888|9||9||10|||||NA|NA|NA|NA|NA|NA|32|NA|NA|256|29|2|2|2|2|NA|2|2|4|NA|2|2|53535|3||||||1||||||2|2|2 \ No newline at end of file From 56308d2be8fca1695aa496f278ffd66d7cd63836 Mon Sep 17 00:00:00 2001 From: Meis Date: Thu, 17 Mar 2022 14:51:37 -0600 Subject: [PATCH 11/20] [EditsNav] Display appropriate label when no edits exists --- src/filing/submission/Nav.jsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/filing/submission/Nav.jsx b/src/filing/submission/Nav.jsx index 0efebf9e3..44505a31b 100644 --- a/src/filing/submission/Nav.jsx +++ b/src/filing/submission/Nav.jsx @@ -31,7 +31,7 @@ export default class EditsNav extends Component { isCompleted: () => this.props.code > VALIDATING, errorClass: 'error', errorText: 'uploaded with formatting errors', - completedText: 'uploaded', + completedText: () => 'uploaded', link: 'upload' }, 'syntactical & validity edits': { @@ -45,7 +45,7 @@ export default class EditsNav extends Component { this.props.code === NO_SYNTACTICAL_VALIDITY_EDITS, errorClass: 'warning-exclamation', errorText: 'syntactical & validity edits found', - completedText: 'no syntactical & validity edits', + completedText: () => 'no syntactical & validity edits', link: 'syntacticalvalidity' }, 'quality edits': { @@ -58,7 +58,9 @@ export default class EditsNav extends Component { (this.props.qualityVerified || !this.props.qualityExists), errorClass: 'warning-question', errorText: 'quality edits found', - completedText: 'quality edits verified', + completedText: () => !this.props.qualityExists + ? 'no quality edits' + : 'quality edits verified', link: 'quality' }, 'macro quality edits': { @@ -70,14 +72,16 @@ export default class EditsNav extends Component { (!this.props.macroExists || this.props.macroVerified), errorClass: 'warning-question', errorText: 'macro quality edits found', - completedText: 'macro quality edits verified', + completedText: () => !this.props.macroExists + ? 'no macro quality edits' + : 'macro quality edits verified', link: 'macro' }, submission: { isReachable: () => this.props.code >= VALIDATED || this.props.code === NO_MACRO_EDITS, isErrored: () => false, isCompleted: () => this.props.code === SIGNED, - completedText: 'submitted', + completedText: () => 'submitted', link: 'submission' } } @@ -129,7 +133,7 @@ export default class EditsNav extends Component { const renderedName = errored ? navItem.errorText : completed - ? navItem.completedText + ? navItem.completedText() : name let navClass = errored From 3419ca83315fba2303ed8479e1b3f6688fc82c39 Mon Sep 17 00:00:00 2001 From: Meis Date: Thu, 17 Mar 2022 16:53:31 -0600 Subject: [PATCH 12/20] [Filing] Direct users to the next actionable step --- src/filing/submission/NavButton.jsx | 31 ++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/filing/submission/NavButton.jsx b/src/filing/submission/NavButton.jsx index 7edbb5312..aa814689d 100644 --- a/src/filing/submission/NavButton.jsx +++ b/src/filing/submission/NavButton.jsx @@ -4,33 +4,54 @@ import { Link } from 'react-router-dom' import Loading from '../../common/LoadingIcon.jsx' import { VALIDATING, - SYNTACTICAL_VALIDITY_EDITS + SYNTACTICAL_VALIDITY_EDITS, + VALIDATED, } from '../constants/statusCodes.js' import './NavButton.css' +const nextActionableStep = ({ + qualityExists, + qualityVerified, + macroExists, + macroVerified, + code, +}) => { + if (code === VALIDATED) return 'submission' + if (qualityExists && !qualityVerified) return 'quality' + if (macroExists && !macroVerified) return 'macro' + return 'syntacticalvalidity' +} + const NavButton = ({ page, base, code, editsFetched, validationComplete, qualityExists, qualityVerified, macroExists, macroVerified }) => { let className let suffix let spinOn = false const editFetchInProgress = code < 14 && !editsFetched const preError = code <= VALIDATING || !validationComplete + const actionableArgs = { + code, + qualityExists, + qualityVerified, + macroExists, + macroVerified, + } switch (page) { case 'upload': - suffix = 'syntacticalvalidity' + suffix = nextActionableStep(actionableArgs) if (preError || editFetchInProgress) className = 'hidden' if (editFetchInProgress && code > VALIDATING && code !== 8 && code !== 11) spinOn = true break case 'syntacticalvalidity': - suffix = 'quality' + suffix = nextActionableStep(actionableArgs) if (preError || code === SYNTACTICAL_VALIDITY_EDITS || editFetchInProgress) className = 'hidden' break case 'quality': - suffix = 'macro' + suffix = nextActionableStep(actionableArgs) if (preError || (qualityExists && !qualityVerified) || editFetchInProgress) className = 'hidden' break case 'macro': - suffix = 'submission' + suffix = nextActionableStep(actionableArgs) if (preError || (macroExists && !macroVerified) || editFetchInProgress) className = 'hidden' break default: From a76bdbb9dce4f98bd100de05f938ffec105d52c2 Mon Sep 17 00:00:00 2001 From: Meis Date: Thu, 17 Mar 2022 17:06:45 -0600 Subject: [PATCH 13/20] [SubmissionNav] Display appropriate label when no edits exists --- src/filing/institutions/Progress.jsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/filing/institutions/Progress.jsx b/src/filing/institutions/Progress.jsx index 995f35bbd..8bd0303e2 100644 --- a/src/filing/institutions/Progress.jsx +++ b/src/filing/institutions/Progress.jsx @@ -20,31 +20,31 @@ const navMap = { submission.status.code === PARSED_WITH_ERRORS || submission.isStalled, isCompleted: submission => submission.status.code > UPLOADED, errorText: 'upload error', - completedText: 'uploaded' + completedText: () => 'uploaded' }, 'syntactical & validity edits': { isErrored: submission => submission.status.code === SYNTACTICAL_VALIDITY_EDITS, isCompleted: submission => submission.status.code >= NO_SYNTACTICAL_VALIDITY_EDITS, errorText: 'syntactical & validity edits', - completedText: 'no syntactical & validity edits' + completedText: () => 'no syntactical & validity edits' }, 'quality edits': { isErrored: submission => submission.qualityExists && !submission.qualityVerified, isCompleted: submission => submission.status.code >= NO_QUALITY_EDITS && (!submission.qualityExists || submission.qualityVerified), errorText: 'quality edits' , - completedText: 'quality edits verified' + completedText: (submission) => submission.qualityExists ? 'quality edits verified' : 'no quality edits' }, 'macro quality edits': { isErrored: submission => submission.macroExists && !submission.macroVerified, isCompleted: submission => (submission.status.code > MACRO_EDITS || submission.status.code === NO_MACRO_EDITS) && (!submission.macroExists || submission.macroVerified), errorText: 'macro quality edits', - completedText: 'macro quality edits verified' + completedText: (submission) => submission.macroExists ? 'macro quality edits verified' : 'no macro edits' }, submission: { isReachable: submission => submission.status.code >= VALIDATED || submission.status.code === NO_MACRO_EDITS , isErrored: () => false, isCompleted: submission => submission.status.code === SIGNED, - completedText: 'submitted' + completedText: () => 'submitted' } } @@ -58,7 +58,7 @@ const renderNavItem = (submission, name, i) => { renderedName = navItem.errorText navClass = 'error' } else if (completed) { - renderedName = navItem.completedText + renderedName = navItem.completedText(submission) navClass = 'complete' } From 264374a6eadf2d71d6be397271deaab05b579134 Mon Sep 17 00:00:00 2001 From: Meis Date: Mon, 28 Mar 2022 12:51:42 -0600 Subject: [PATCH 14/20] Remove TODO. Clean files process successfully without UI issue --- src/filing/submission/upload/ValidationProgress.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/filing/submission/upload/ValidationProgress.jsx b/src/filing/submission/upload/ValidationProgress.jsx index 6b13c75cd..74829d0cf 100644 --- a/src/filing/submission/upload/ValidationProgress.jsx +++ b/src/filing/submission/upload/ValidationProgress.jsx @@ -8,10 +8,6 @@ import { NO_MACRO_EDITS, UPLOADING } from '../../constants/statusCodes.js' -/* TODO -we may need to update this -we'll have to see what a clean file upload does -*/ import './ValidationProgress.css' From aa5ea1410435b4f0ba5fdc1be6dadacd94e10f2d Mon Sep 17 00:00:00 2001 From: Meis Date: Mon, 28 Mar 2022 15:28:35 -0600 Subject: [PATCH 15/20] [Filing] Support both Secure/Plain websockets based on the App protocol --- nginx/nginx.conf | 2 +- src/App.jsx | 2 ++ src/filing/actions/listenForProgress.js | 4 +++- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index bc36348c6..bcd236582 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -23,7 +23,7 @@ http { add_header Strict-Transport-Security 'max-age=31536000; includeSubDomains; preload'; # CSP - add_header Content-Security-Policy "default-src 'self' blob:; script-src 'self' 'unsafe-inline' blob: data: https://tagmanager.google.com https://www.googletagmanager.com https://www.google-analytics.com https://*.cfpb.gov https://www.consumerfinance.gov; img-src 'self' blob: data: https://www.google-analytics.com https://raw.githubusercontent.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; frame-src 'self' https://www.youtube.com/ https://ffiec.cfpb.gov/; connect-src 'self' ws://*.cfpb.gov https://*.cfpb.gov https://www.consumerfinance.gov https://raw.githubusercontent.com https://ffiec-api.cfpb.gov https://ffiec.cfpb.gov https://*.mapbox.com https://www.google-analytics.com https://s3.amazonaws.com;"; + add_header Content-Security-Policy "default-src 'self' blob:; script-src 'self' 'unsafe-inline' blob: data: https://tagmanager.google.com https://www.googletagmanager.com https://www.google-analytics.com https://*.cfpb.gov https://www.consumerfinance.gov; img-src 'self' blob: data: https://www.google-analytics.com https://raw.githubusercontent.com; style-src 'self' 'unsafe-inline'; font-src 'self' data:; object-src 'none'; frame-src 'self' https://www.youtube.com/ https://ffiec.cfpb.gov/; connect-src 'self' ws://*.cfpb.gov wss://*.cfpb.gov https://*.cfpb.gov https://www.consumerfinance.gov https://raw.githubusercontent.com https://ffiec-api.cfpb.gov https://ffiec.cfpb.gov https://*.mapbox.com https://www.google-analytics.com https://s3.amazonaws.com;"; # Restrict referrer add_header Referrer-Policy "strict-origin"; diff --git a/src/App.jsx b/src/App.jsx index 091da7e15..30f8bde23 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -67,6 +67,8 @@ const App = () => { {showFooter &&