diff --git a/package-lock.json b/package-lock.json index 336f276f98..84671aa29c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7458,11 +7458,6 @@ "d3-color": "1" } }, - "d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" - }, "d3-sankey": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.7.1.tgz", @@ -7471,6 +7466,21 @@ "d3-array": "1", "d3-collection": "1", "d3-shape": "^1.2.0" + }, + "dependencies": { + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + } } }, "d3-sankey0.12.3": { @@ -7480,6 +7490,21 @@ "requires": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" + }, + "dependencies": { + "d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "requires": { + "d3-path": "1" + } + } } }, "d3-scale": { @@ -7501,14 +7526,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" }, - "d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "requires": { - "d3-path": "1" - } - }, "d3-time": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", @@ -11039,11 +11056,6 @@ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true }, - "i": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", - "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=" - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -17243,1771 +17255,6 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" }, - "npm": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-7.8.0.tgz", - "integrity": "sha512-9AC3Dj9OUWaUdmTmEVttE/1MWkfF7+sAKPRo9tKEyjo49AXmHQBn+RC33M9dima91mEMqDIA71xyRm4VmhDipg==", - "requires": { - "@npmcli/arborist": "^2.3.0", - "@npmcli/ci-detect": "^1.2.0", - "@npmcli/config": "^2.1.0", - "@npmcli/run-script": "^1.8.4", - "abbrev": "~1.1.1", - "ansicolors": "~0.3.2", - "ansistyles": "~0.1.3", - "archy": "~1.0.0", - "byte-size": "^7.0.1", - "cacache": "^15.0.6", - "chalk": "^4.1.0", - "chownr": "^2.0.0", - "cli-columns": "^3.1.2", - "cli-table3": "^0.6.0", - "columnify": "~1.5.4", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "hosted-git-info": "^4.0.2", - "ini": "^2.0.0", - "init-package-json": "^2.0.2", - "is-cidr": "^4.0.2", - "json-parse-even-better-errors": "^2.3.1", - "leven": "^3.1.0", - "libnpmaccess": "^4.0.1", - "libnpmdiff": "^2.0.4", - "libnpmfund": "^1.0.2", - "libnpmhook": "^6.0.1", - "libnpmorg": "^2.0.1", - "libnpmpack": "^2.0.1", - "libnpmpublish": "^4.0.0", - "libnpmsearch": "^3.1.0", - "libnpmteam": "^2.0.2", - "libnpmversion": "^1.1.0", - "make-fetch-happen": "^8.0.14", - "minipass": "^3.1.3", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "mkdirp-infer-owner": "^2.0.0", - "ms": "^2.1.2", - "node-gyp": "^7.1.2", - "nopt": "^5.0.0", - "npm-audit-report": "^2.1.4", - "npm-package-arg": "^8.1.2", - "npm-pick-manifest": "^6.1.1", - "npm-profile": "^5.0.2", - "npm-registry-fetch": "^9.0.0", - "npm-user-validate": "^1.0.1", - "npmlog": "~4.1.2", - "opener": "^1.5.2", - "pacote": "^11.3.1", - "parse-conflict-json": "^1.1.1", - "qrcode-terminal": "^0.12.0", - "read": "~1.0.7", - "read-package-json": "^3.0.1", - "read-package-json-fast": "^2.0.2", - "readdir-scoped-modules": "^1.1.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "ssri": "^8.0.1", - "tar": "^6.1.0", - "text-table": "~0.2.0", - "tiny-relative-date": "^1.3.0", - "treeverse": "^1.0.4", - "validate-npm-package-name": "~3.0.0", - "which": "^2.0.2", - "write-file-atomic": "^3.0.3" - }, - "dependencies": { - "@npmcli/arborist": { - "version": "2.3.0", - "bundled": true, - "requires": { - "@npmcli/installed-package-contents": "^1.0.7", - "@npmcli/map-workspaces": "^1.0.2", - "@npmcli/metavuln-calculator": "^1.1.0", - "@npmcli/move-file": "^1.1.0", - "@npmcli/name-from-folder": "^1.0.1", - "@npmcli/node-gyp": "^1.0.1", - "@npmcli/run-script": "^1.8.2", - "bin-links": "^2.2.1", - "cacache": "^15.0.3", - "common-ancestor-path": "^1.0.1", - "json-parse-even-better-errors": "^2.3.1", - "json-stringify-nice": "^1.1.2", - "mkdirp-infer-owner": "^2.0.0", - "npm-install-checks": "^4.0.0", - "npm-package-arg": "^8.1.0", - "npm-pick-manifest": "^6.1.0", - "npm-registry-fetch": "^9.0.0", - "pacote": "^11.2.6", - "parse-conflict-json": "^1.1.1", - "promise-all-reject-late": "^1.0.0", - "promise-call-limit": "^1.0.1", - "read-package-json-fast": "^2.0.2", - "readdir-scoped-modules": "^1.1.0", - "semver": "^7.3.5", - "tar": "^6.1.0", - "treeverse": "^1.0.4", - "walk-up-path": "^1.0.0" - } - }, - "@npmcli/ci-detect": { - "version": "1.3.0", - "bundled": true - }, - "@npmcli/config": { - "version": "2.1.0", - "bundled": true, - "requires": { - "ini": "^2.0.0", - "mkdirp-infer-owner": "^2.0.0", - "nopt": "^5.0.0", - "semver": "^7.3.4", - "walk-up-path": "^1.0.0" - } - }, - "@npmcli/disparity-colors": { - "version": "1.0.1", - "bundled": true, - "requires": { - "ansi-styles": "^4.3.0" - } - }, - "@npmcli/git": { - "version": "2.0.6", - "bundled": true, - "requires": { - "@npmcli/promise-spawn": "^1.1.0", - "lru-cache": "^6.0.0", - "mkdirp": "^1.0.3", - "npm-pick-manifest": "^6.0.0", - "promise-inflight": "^1.0.1", - "promise-retry": "^2.0.1", - "semver": "^7.3.2", - "unique-filename": "^1.1.1", - "which": "^2.0.2" - } - }, - "@npmcli/installed-package-contents": { - "version": "1.0.7", - "bundled": true, - "requires": { - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "@npmcli/map-workspaces": { - "version": "1.0.3", - "bundled": true, - "requires": { - "@npmcli/name-from-folder": "^1.0.1", - "glob": "^7.1.6", - "minimatch": "^3.0.4", - "read-package-json-fast": "^2.0.1" - } - }, - "@npmcli/metavuln-calculator": { - "version": "1.1.1", - "bundled": true, - "requires": { - "cacache": "^15.0.5", - "pacote": "^11.1.11", - "semver": "^7.3.2" - } - }, - "@npmcli/move-file": { - "version": "1.1.2", - "bundled": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, - "@npmcli/name-from-folder": { - "version": "1.0.1", - "bundled": true - }, - "@npmcli/node-gyp": { - "version": "1.0.2", - "bundled": true - }, - "@npmcli/promise-spawn": { - "version": "1.3.2", - "bundled": true, - "requires": { - "infer-owner": "^1.0.4" - } - }, - "@npmcli/run-script": { - "version": "1.8.4", - "bundled": true, - "requires": { - "@npmcli/node-gyp": "^1.0.2", - "@npmcli/promise-spawn": "^1.3.2", - "infer-owner": "^1.0.4", - "node-gyp": "^7.1.0", - "read-package-json-fast": "^2.0.1" - } - }, - "@tootallnate/once": { - "version": "1.1.2", - "bundled": true - }, - "abbrev": { - "version": "1.1.1", - "bundled": true - }, - "agent-base": { - "version": "6.0.2", - "bundled": true, - "requires": { - "debug": "4" - } - }, - "agentkeepalive": { - "version": "4.1.4", - "bundled": true, - "requires": { - "debug": "^4.1.0", - "depd": "^1.1.2", - "humanize-ms": "^1.2.1" - } - }, - "aggregate-error": { - "version": "3.1.0", - "bundled": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "bundled": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "ansi-styles": { - "version": "4.3.0", - "bundled": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "ansicolors": { - "version": "0.3.2", - "bundled": true - }, - "ansistyles": { - "version": "0.1.3", - "bundled": true - }, - "aproba": { - "version": "2.0.0", - "bundled": true - }, - "archy": { - "version": "1.0.0", - "bundled": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "asap": { - "version": "2.0.6", - "bundled": true - }, - "asn1": { - "version": "0.2.4", - "bundled": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "bundled": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true - }, - "aws-sign2": { - "version": "0.7.0", - "bundled": true - }, - "aws4": { - "version": "1.11.0", - "bundled": true - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "bundled": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bin-links": { - "version": "2.2.1", - "bundled": true, - "requires": { - "cmd-shim": "^4.0.1", - "mkdirp": "^1.0.3", - "npm-normalize-package-bin": "^1.0.0", - "read-cmd-shim": "^2.0.0", - "rimraf": "^3.0.0", - "write-file-atomic": "^3.0.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "builtins": { - "version": "1.0.3", - "bundled": true - }, - "byte-size": { - "version": "7.0.1", - "bundled": true - }, - "cacache": { - "version": "15.0.6", - "bundled": true, - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - } - }, - "caseless": { - "version": "0.12.0", - "bundled": true - }, - "chalk": { - "version": "4.1.0", - "bundled": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chownr": { - "version": "2.0.0", - "bundled": true - }, - "cidr-regex": { - "version": "3.1.1", - "bundled": true, - "requires": { - "ip-regex": "^4.1.0" - } - }, - "clean-stack": { - "version": "2.2.0", - "bundled": true - }, - "cli-columns": { - "version": "3.1.2", - "bundled": true, - "requires": { - "string-width": "^2.0.0", - "strip-ansi": "^3.0.1" - } - }, - "cli-table3": { - "version": "0.6.0", - "bundled": true, - "requires": { - "colors": "^1.1.2", - "object-assign": "^4.1.0", - "string-width": "^4.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "bundled": true - }, - "string-width": { - "version": "4.2.2", - "bundled": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "bundled": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "clone": { - "version": "1.0.4", - "bundled": true - }, - "cmd-shim": { - "version": "4.1.0", - "bundled": true, - "requires": { - "mkdirp-infer-owner": "^2.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "color-convert": { - "version": "2.0.1", - "bundled": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "bundled": true - }, - "colors": { - "version": "1.4.0", - "bundled": true, - "optional": true - }, - "columnify": { - "version": "1.5.4", - "bundled": true, - "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" - } - }, - "combined-stream": { - "version": "1.0.8", - "bundled": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "common-ancestor-path": { - "version": "1.0.1", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "debug": { - "version": "4.3.1", - "bundled": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "bundled": true - } - } - }, - "debuglog": { - "version": "1.0.1", - "bundled": true - }, - "defaults": { - "version": "1.0.3", - "bundled": true, - "requires": { - "clone": "^1.0.2" - } - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true - }, - "depd": { - "version": "1.1.2", - "bundled": true - }, - "dezalgo": { - "version": "1.0.3", - "bundled": true, - "requires": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "diff": { - "version": "5.0.0", - "bundled": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "bundled": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "emoji-regex": { - "version": "8.0.0", - "bundled": true - }, - "encoding": { - "version": "0.1.13", - "bundled": true, - "optional": true, - "requires": { - "iconv-lite": "^0.6.2" - } - }, - "env-paths": { - "version": "2.2.1", - "bundled": true - }, - "err-code": { - "version": "2.0.3", - "bundled": true - }, - "extend": { - "version": "3.0.2", - "bundled": true - }, - "extsprintf": { - "version": "1.3.0", - "bundled": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "bundled": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "bundled": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true - }, - "form-data": { - "version": "2.3.3", - "bundled": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fs-minipass": { - "version": "2.1.0", - "bundled": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true - }, - "function-bind": { - "version": "1.1.1", - "bundled": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - }, - "dependencies": { - "aproba": { - "version": "1.2.0", - "bundled": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - } - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "bundled": true - }, - "har-schema": { - "version": "2.0.0", - "bundled": true - }, - "har-validator": { - "version": "5.1.5", - "bundled": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "bundled": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "bundled": true - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true - }, - "hosted-git-info": { - "version": "4.0.2", - "bundled": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "http-cache-semantics": { - "version": "4.1.0", - "bundled": true - }, - "http-proxy-agent": { - "version": "4.0.1", - "bundled": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "http-signature": { - "version": "1.2.0", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "bundled": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "humanize-ms": { - "version": "1.2.1", - "bundled": true, - "requires": { - "ms": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.6.2", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "imurmurhash": { - "version": "0.1.4", - "bundled": true - }, - "indent-string": { - "version": "4.0.0", - "bundled": true - }, - "infer-owner": { - "version": "1.0.4", - "bundled": true - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true - }, - "ini": { - "version": "2.0.0", - "bundled": true - }, - "init-package-json": { - "version": "2.0.2", - "bundled": true, - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^8.1.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "^3.0.0", - "semver": "^7.3.2", - "validate-npm-package-license": "^3.0.4", - "validate-npm-package-name": "^3.0.0" - } - }, - "ip": { - "version": "1.1.5", - "bundled": true - }, - "ip-regex": { - "version": "4.3.0", - "bundled": true - }, - "is-cidr": { - "version": "4.0.2", - "bundled": true, - "requires": { - "cidr-regex": "^3.1.1" - } - }, - "is-core-module": { - "version": "2.2.0", - "bundled": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "bundled": true - }, - "is-lambda": { - "version": "1.0.1", - "bundled": true - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true - }, - "isexe": { - "version": "2.0.0", - "bundled": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true - }, - "jsbn": { - "version": "0.1.1", - "bundled": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "bundled": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "bundled": true - }, - "json-stringify-nice": { - "version": "1.1.3", - "bundled": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true - }, - "jsonparse": { - "version": "1.3.1", - "bundled": true - }, - "jsprim": { - "version": "1.4.1", - "bundled": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "just-diff": { - "version": "3.0.2", - "bundled": true - }, - "just-diff-apply": { - "version": "3.0.0", - "bundled": true - }, - "leven": { - "version": "3.1.0", - "bundled": true - }, - "libnpmaccess": { - "version": "4.0.1", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "minipass": "^3.1.1", - "npm-package-arg": "^8.0.0", - "npm-registry-fetch": "^9.0.0" - } - }, - "libnpmdiff": { - "version": "2.0.4", - "bundled": true, - "requires": { - "@npmcli/disparity-colors": "^1.0.1", - "@npmcli/installed-package-contents": "^1.0.7", - "binary-extensions": "^2.2.0", - "diff": "^5.0.0", - "minimatch": "^3.0.4", - "npm-package-arg": "^8.1.1", - "pacote": "^11.3.0", - "tar": "^6.1.0" - } - }, - "libnpmfund": { - "version": "1.0.2", - "bundled": true, - "requires": { - "@npmcli/arborist": "^2.0.0" - } - }, - "libnpmhook": { - "version": "6.0.1", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^9.0.0" - } - }, - "libnpmorg": { - "version": "2.0.1", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^9.0.0" - } - }, - "libnpmpack": { - "version": "2.0.1", - "bundled": true, - "requires": { - "@npmcli/run-script": "^1.8.3", - "npm-package-arg": "^8.1.0", - "pacote": "^11.2.6" - } - }, - "libnpmpublish": { - "version": "4.0.0", - "bundled": true, - "requires": { - "normalize-package-data": "^3.0.0", - "npm-package-arg": "^8.1.0", - "npm-registry-fetch": "^9.0.0", - "semver": "^7.1.3", - "ssri": "^8.0.0" - } - }, - "libnpmsearch": { - "version": "3.1.0", - "bundled": true, - "requires": { - "npm-registry-fetch": "^9.0.0" - } - }, - "libnpmteam": { - "version": "2.0.2", - "bundled": true, - "requires": { - "aproba": "^2.0.0", - "npm-registry-fetch": "^9.0.0" - } - }, - "libnpmversion": { - "version": "1.1.0", - "bundled": true, - "requires": { - "@npmcli/git": "^2.0.6", - "@npmcli/run-script": "^1.8.3", - "json-parse-even-better-errors": "^2.3.1", - "semver": "^7.3.4", - "stringify-package": "^1.0.1" - } - }, - "lru-cache": { - "version": "6.0.0", - "bundled": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-fetch-happen": { - "version": "8.0.14", - "bundled": true, - "requires": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.0.5", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^5.0.0", - "ssri": "^8.0.0" - } - }, - "mime-db": { - "version": "1.46.0", - "bundled": true - }, - "mime-types": { - "version": "2.1.29", - "bundled": true, - "requires": { - "mime-db": "1.46.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minipass": { - "version": "3.1.3", - "bundled": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "bundled": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-fetch": { - "version": "1.3.3", - "bundled": true, - "requires": { - "encoding": "^0.1.12", - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "bundled": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-json-stream": { - "version": "1.0.1", - "bundled": true, - "requires": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "bundled": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-sized": { - "version": "1.0.3", - "bundled": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "bundled": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "bundled": true - }, - "mkdirp-infer-owner": { - "version": "2.0.0", - "bundled": true, - "requires": { - "chownr": "^2.0.0", - "infer-owner": "^1.0.4", - "mkdirp": "^1.0.3" - } - }, - "ms": { - "version": "2.1.3", - "bundled": true - }, - "mute-stream": { - "version": "0.0.8", - "bundled": true - }, - "node-gyp": { - "version": "7.1.2", - "bundled": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.3", - "nopt": "^5.0.0", - "npmlog": "^4.1.2", - "request": "^2.88.2", - "rimraf": "^3.0.2", - "semver": "^7.3.2", - "tar": "^6.0.2", - "which": "^2.0.2" - } - }, - "nopt": { - "version": "5.0.0", - "bundled": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "3.0.2", - "bundled": true, - "requires": { - "hosted-git-info": "^4.0.1", - "resolve": "^1.20.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "npm-audit-report": { - "version": "2.1.4", - "bundled": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-install-checks": { - "version": "4.0.0", - "bundled": true, - "requires": { - "semver": "^7.1.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true - }, - "npm-package-arg": { - "version": "8.1.2", - "bundled": true, - "requires": { - "hosted-git-info": "^4.0.1", - "semver": "^7.3.4", - "validate-npm-package-name": "^3.0.0" - } - }, - "npm-packlist": { - "version": "2.1.5", - "bundled": true, - "requires": { - "glob": "^7.1.6", - "ignore-walk": "^3.0.3", - "npm-bundled": "^1.1.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-pick-manifest": { - "version": "6.1.1", - "bundled": true, - "requires": { - "npm-install-checks": "^4.0.0", - "npm-normalize-package-bin": "^1.0.1", - "npm-package-arg": "^8.1.2", - "semver": "^7.3.4" - } - }, - "npm-profile": { - "version": "5.0.2", - "bundled": true, - "requires": { - "npm-registry-fetch": "^9.0.0" - } - }, - "npm-registry-fetch": { - "version": "9.0.0", - "bundled": true, - "requires": { - "@npmcli/ci-detect": "^1.0.0", - "lru-cache": "^6.0.0", - "make-fetch-happen": "^8.0.9", - "minipass": "^3.1.3", - "minipass-fetch": "^1.3.0", - "minipass-json-stream": "^1.0.1", - "minizlib": "^2.0.0", - "npm-package-arg": "^8.0.0" - } - }, - "npm-user-validate": { - "version": "1.0.1", - "bundled": true - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true - }, - "oauth-sign": { - "version": "0.9.0", - "bundled": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "opener": { - "version": "1.5.2", - "bundled": true - }, - "p-map": { - "version": "4.0.0", - "bundled": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "pacote": { - "version": "11.3.1", - "bundled": true, - "requires": { - "@npmcli/git": "^2.0.1", - "@npmcli/installed-package-contents": "^1.0.6", - "@npmcli/promise-spawn": "^1.2.0", - "@npmcli/run-script": "^1.8.2", - "cacache": "^15.0.5", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "infer-owner": "^1.0.4", - "minipass": "^3.1.3", - "mkdirp": "^1.0.3", - "npm-package-arg": "^8.0.1", - "npm-packlist": "^2.1.4", - "npm-pick-manifest": "^6.0.0", - "npm-registry-fetch": "^9.0.0", - "promise-retry": "^2.0.1", - "read-package-json-fast": "^2.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.1", - "tar": "^6.1.0" - } - }, - "parse-conflict-json": { - "version": "1.1.1", - "bundled": true, - "requires": { - "json-parse-even-better-errors": "^2.3.0", - "just-diff": "^3.0.1", - "just-diff-apply": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true - }, - "path-parse": { - "version": "1.0.6", - "bundled": true - }, - "performance-now": { - "version": "2.1.0", - "bundled": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true - }, - "promise-all-reject-late": { - "version": "1.0.1", - "bundled": true - }, - "promise-call-limit": { - "version": "1.0.1", - "bundled": true - }, - "promise-inflight": { - "version": "1.0.1", - "bundled": true - }, - "promise-retry": { - "version": "2.0.1", - "bundled": true, - "requires": { - "err-code": "^2.0.2", - "retry": "^0.12.0" - } - }, - "promzard": { - "version": "0.3.0", - "bundled": true, - "requires": { - "read": "1" - } - }, - "psl": { - "version": "1.8.0", - "bundled": true - }, - "punycode": { - "version": "2.1.1", - "bundled": true - }, - "qrcode-terminal": { - "version": "0.12.0", - "bundled": true - }, - "qs": { - "version": "6.5.2", - "bundled": true - }, - "read": { - "version": "1.0.7", - "bundled": true, - "requires": { - "mute-stream": "~0.0.4" - } - }, - "read-cmd-shim": { - "version": "2.0.0", - "bundled": true - }, - "read-package-json": { - "version": "3.0.1", - "bundled": true, - "requires": { - "glob": "^7.1.1", - "json-parse-even-better-errors": "^2.3.0", - "normalize-package-data": "^3.0.0", - "npm-normalize-package-bin": "^1.0.0" - } - }, - "read-package-json-fast": { - "version": "2.0.2", - "bundled": true, - "requires": { - "json-parse-even-better-errors": "^2.3.0", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "readdir-scoped-modules": { - "version": "1.1.0", - "bundled": true, - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" - } - }, - "request": { - "version": "2.88.2", - "bundled": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "tough-cookie": { - "version": "2.5.0", - "bundled": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - } - } - }, - "resolve": { - "version": "1.20.0", - "bundled": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "retry": { - "version": "0.12.0", - "bundled": true - }, - "rimraf": { - "version": "3.0.2", - "bundled": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true - }, - "semver": { - "version": "7.3.5", - "bundled": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true - }, - "signal-exit": { - "version": "3.0.3", - "bundled": true - }, - "smart-buffer": { - "version": "4.1.0", - "bundled": true - }, - "socks": { - "version": "2.6.0", - "bundled": true, - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.1.0" - } - }, - "socks-proxy-agent": { - "version": "5.0.0", - "bundled": true, - "requires": { - "agent-base": "6", - "debug": "4", - "socks": "^2.3.3" - } - }, - "spdx-correct": { - "version": "3.1.1", - "bundled": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "bundled": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "bundled": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.7", - "bundled": true - }, - "sshpk": { - "version": "1.16.1", - "bundled": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "8.0.1", - "bundled": true, - "requires": { - "minipass": "^3.1.1" - } - }, - "string-width": { - "version": "2.1.1", - "bundled": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "bundled": true - }, - "strip-ansi": { - "version": "4.0.0", - "bundled": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-package": { - "version": "1.0.1", - "bundled": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "7.2.0", - "bundled": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "tar": { - "version": "6.1.0", - "bundled": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "bundled": true - }, - "tiny-relative-date": { - "version": "1.3.0", - "bundled": true - }, - "treeverse": { - "version": "1.0.4", - "bundled": true - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "bundled": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "unique-filename": { - "version": "1.1.1", - "bundled": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "bundled": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, - "uri-js": { - "version": "4.4.1", - "bundled": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true - }, - "uuid": { - "version": "3.4.0", - "bundled": true - }, - "validate-npm-package-license": { - "version": "3.0.4", - "bundled": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "validate-npm-package-name": { - "version": "3.0.0", - "bundled": true, - "requires": { - "builtins": "^1.0.3" - } - }, - "verror": { - "version": "1.10.0", - "bundled": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "walk-up-path": { - "version": "1.0.0", - "bundled": true - }, - "wcwidth": { - "version": "1.0.1", - "bundled": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "which": { - "version": "2.0.2", - "bundled": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "write-file-atomic": { - "version": "3.0.3", - "bundled": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "yallist": { - "version": "4.0.0", - "bundled": true - } - } - }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", diff --git a/package.json b/package.json index 2744e8b3c3..38f0329ec8 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,6 @@ "d3-sankey": "0.7.1", "d3-sankey0.12.3": "npm:d3-sankey@0.12.3", "d3-scale": "^1.0.4", - "d3-time": "^1.0.11", "data-transparency-ui": "github:fedspendingtransparency/data-transparency-ui#v2.2.1", "date-fns": "^2.16.1", "file-loader": "^3.0.1", diff --git a/scripts/sitemaps/pages.js b/scripts/sitemaps/pages.js index a1f8ee91c3..743b6a07b4 100644 --- a/scripts/sitemaps/pages.js +++ b/scripts/sitemaps/pages.js @@ -21,7 +21,8 @@ const routes = [ '/state', '/recipient', '/disaster/covid-19', - '/disaster/covid-19/data-sources' + '/disaster/covid-19/data-sources', + '/data-dictionary' ]; const recipientRequestObject = { diff --git a/src/_scss/pages/agencyV2/index.scss b/src/_scss/pages/agencyV2/index.scss index f0ff9c1b16..21aa5c7e07 100644 --- a/src/_scss/pages/agencyV2/index.scss +++ b/src/_scss/pages/agencyV2/index.scss @@ -76,7 +76,7 @@ .usda-section__container { @import './tabs'; @include display(flex); - @import './overview'; + @import './overview/overview'; hr { height: rem(2) } diff --git a/src/_scss/pages/agencyV2/overview/_fySummary.scss b/src/_scss/pages/agencyV2/overview/_fySummary.scss new file mode 100644 index 0000000000..a501ba3c6e --- /dev/null +++ b/src/_scss/pages/agencyV2/overview/_fySummary.scss @@ -0,0 +1,38 @@ +.fy-summary { + padding-top: rem(50); + .fy-summary__heading { + font-size: rem(14); + line-height: rem(16); + margin: 0; + @include media($medium-screen) { // Desktop + font-size: rem(18); + font-weight: $font-semibold; + line-height: rem(23); + } + } + hr { + height: rem(1); + background-color: $color-gray-light; + margin-top: rem(5); + } + .fy-summary__row { // Desktop + @include display(flex); + padding: rem(30) 0 rem(22); + .fy-summary__col { + @include flex (1 1 25%); + padding: 0 rem(10); + border-right: solid rem(1) $color-gray-lighter; + &:first-of-type { + padding-left: 0; + } + &:last-of-type { + padding-right: 0; + border-right: 0; + } + } + } + .usa-dt-carousel { // Mobile + padding-top: rem(25); + } + @import './visualizationSection'; +} \ No newline at end of file diff --git a/src/_scss/pages/agencyV2/_overview.scss b/src/_scss/pages/agencyV2/overview/_overview.scss similarity index 96% rename from src/_scss/pages/agencyV2/_overview.scss rename to src/_scss/pages/agencyV2/overview/_overview.scss index e0e5ab09bb..d67ad01768 100644 --- a/src/_scss/pages/agencyV2/_overview.scss +++ b/src/_scss/pages/agencyV2/overview/_overview.scss @@ -1,4 +1,5 @@ .agency-overview { + @import './fySummary'; width: 100%; // fix for IE .agency-overview__top { @include display(flex); @@ -72,4 +73,5 @@ @include button-link; margin-top: 0.5rem; } -} \ No newline at end of file + @import './visualizations/totalObligationsOverTime'; +} diff --git a/src/_scss/pages/agencyV2/overview/_visualizationSection.scss b/src/_scss/pages/agencyV2/overview/_visualizationSection.scss new file mode 100644 index 0000000000..af1394e9c9 --- /dev/null +++ b/src/_scss/pages/agencyV2/overview/_visualizationSection.scss @@ -0,0 +1,39 @@ +.visualization-section { + text-align: center; + height: 100%; + .visualization-section__subtitle { + padding-bottom: rem(8); + } + .visualization-section__data { + font-size: rem(27); + font-weight: $font-semibold; + line-height: rem(34); + padding-bottom: rem(5); + } + .visualization-section__secondary-data { + font-size: $small-font-size; + padding-bottom: rem(12); + } + .visualization-section__viz-wrapper { + height: rem(208); + @include display(flex); + @include align-items(center); + @include justify-content(center); + /* TODO - remove placeholder styling once all four + visualizations are implemented */ + .viz-placeholder { + padding: rem(40) 0; + } + } + .visualization-section__label { + padding-top: rem(15); + } + @include media($medium-screen) { + // Desktop + .visualization-section__data { + font-size: rem(30); + line-height: rem(38); + padding-bottom: rem(10); + } + } +} diff --git a/src/_scss/pages/agencyV2/visualizations/_totalObligationsOverTime.scss b/src/_scss/pages/agencyV2/visualizations/_totalObligationsOverTime.scss new file mode 100644 index 0000000000..7b9606edd6 --- /dev/null +++ b/src/_scss/pages/agencyV2/visualizations/_totalObligationsOverTime.scss @@ -0,0 +1,39 @@ +.total-obligations-over-time-visualization-container { + .usda-message { + padding: 0; + margin-bottom: rem(100); + } + .total-obligations-over-time-svg { + .total-obligations-over-time-svg-body { + .paths { + .path { + stroke: $color-cool-blue; + stroke-width: 1; + fill: none; + } + .area-path { + fill: $color-cool-blue-lightest; + &.jon { + stroke: $color-cool-blue; + stroke-width: 1; + fill: none; + } + } + } + .total-obligations-over-time-svg-axis { + line { + stroke: $color-gray-light; + stroke-width: 1; + } + .x-axis { + .x-axis-labels { + text { + font-size: rem(14); + text-anchor: middle; + } + } + } + } + } + } +} diff --git a/src/_scss/pages/bulkDownload/bulkDownloadPage.scss b/src/_scss/pages/bulkDownload/bulkDownloadPage.scss index bbb883b9d6..20ce81c6ec 100644 --- a/src/_scss/pages/bulkDownload/bulkDownloadPage.scss +++ b/src/_scss/pages/bulkDownload/bulkDownloadPage.scss @@ -26,7 +26,6 @@ .bulk-download__data { @import "downloadData"; @import "archive/archive"; - @import "dictionary/dictionary"; @import "defCheckbox"; @import "metadataPage"; @include span-columns(16); diff --git a/src/_scss/pages/bulkDownload/dictionary/dictionary.scss b/src/_scss/pages/bulkDownload/dictionary/dictionary.scss deleted file mode 100644 index c0f24c3405..0000000000 --- a/src/_scss/pages/bulkDownload/dictionary/dictionary.scss +++ /dev/null @@ -1,41 +0,0 @@ -.data-dictionary { - @include media($medium-screen) { - padding: 0 rem(30); - } - .data-dictionary__title { - margin-top: 0; - font-weight: 400; - } - .data-dictionary__intro { - font-size: $small-font-size; - } - .data-dictionary__search-download { - @include display(flex); - @include justify-content(space-between); - } - .data-dictionary__download { - @include align-self(center); - padding-bottom: rem(5); - .data-dictionary__download-link { - @include display(flex); - @include align-items(center); - .data-dictionary__download-icon { - @include display(flex); - @include flex(0 0 auto); - height: rem(15); - width: rem(15); - padding-right: rem(5); - .usa-da-spreadsheet { - height: rem(15); - width: rem(15); - } - } - font-size: $small-font-size; - } - } - - .data-dictionary__table-wrapper { - @import "./_dictionaryTable"; - } - @import "./_searchBar"; -} \ No newline at end of file diff --git a/src/_scss/pages/data-sources/index.scss b/src/_scss/pages/data-sources/index.scss index b503987c0d..a7c532f7e7 100644 --- a/src/_scss/pages/data-sources/index.scss +++ b/src/_scss/pages/data-sources/index.scss @@ -22,8 +22,6 @@ .about-content-wrapper { @media(min-width: $medium-screen) { margin: rem(20) rem(20) rem(20) 0; - } - @media(min-width: $tablet-screen) { width: 70%; } .about-padded-content { @@ -39,6 +37,7 @@ } } .about-section-content ol { + font-size: rem(18); &.about-instructions { font-family: monospace; padding: 3rem 5rem; @@ -54,6 +53,11 @@ .about-section-content ul li ul{ margin-bottom: 3rem; } + .about-section-content_custom-list { + list-style-type: none; + padding-left: 1.5rem; + font-style: italic; + } } .about-content { width: 100%; diff --git a/src/_scss/pages/bulkDownload/dictionary/_dictionaryTable.scss b/src/_scss/pages/dataDictionary/_dictionaryTable.scss similarity index 100% rename from src/_scss/pages/bulkDownload/dictionary/_dictionaryTable.scss rename to src/_scss/pages/dataDictionary/_dictionaryTable.scss diff --git a/src/_scss/pages/bulkDownload/dictionary/_header.scss b/src/_scss/pages/dataDictionary/_header.scss similarity index 100% rename from src/_scss/pages/bulkDownload/dictionary/_header.scss rename to src/_scss/pages/dataDictionary/_header.scss diff --git a/src/_scss/pages/bulkDownload/dictionary/_searchBar.scss b/src/_scss/pages/dataDictionary/_searchBar.scss similarity index 100% rename from src/_scss/pages/bulkDownload/dictionary/_searchBar.scss rename to src/_scss/pages/dataDictionary/_searchBar.scss diff --git a/src/_scss/pages/bulkDownload/dictionary/_sorter.scss b/src/_scss/pages/dataDictionary/_sorter.scss similarity index 100% rename from src/_scss/pages/bulkDownload/dictionary/_sorter.scss rename to src/_scss/pages/dataDictionary/_sorter.scss diff --git a/src/_scss/pages/dataDictionary/dataDictionaryPage.scss b/src/_scss/pages/dataDictionary/dataDictionaryPage.scss new file mode 100644 index 0000000000..a874c85f56 --- /dev/null +++ b/src/_scss/pages/dataDictionary/dataDictionaryPage.scss @@ -0,0 +1,60 @@ +.usa-da-data-dictionary-page { + @import 'all'; + @import 'layouts/default/default'; + @import 'layouts/default/stickyHeader/header'; + + #main-content { + @import '../../mixins/fullSectionWrap'; + @import '../../components/pageLoading'; + @include fullSectionWrap(($global-mrg * 2), ($global-mrg * 2)); + + background-color: $color-white; + box-shadow: $container-shadow; + border-top: 1px solid $color-gray-border; + border-right: 1px solid $color-gray-border; + border-bottom: 1px solid $color-gray-border; + padding: rem(30) rem(20); + + .data-dictionary { + @include media($medium-screen) { + padding: 0 rem(30); + } + .data-dictionary__title { + margin-top: 0; + font-weight: 400; + } + .data-dictionary__intro { + font-size: $small-font-size; + } + .data-dictionary__search-download { + @include display(flex); + @include justify-content(space-between); + } + .data-dictionary__download { + @include align-self(center); + padding-bottom: rem(5); + .data-dictionary__download-link { + @include display(flex); + @include align-items(center); + .data-dictionary__download-icon { + @include display(flex); + @include flex(0 0 auto); + height: rem(15); + width: rem(15); + padding-right: rem(5); + .usa-da-spreadsheet { + height: rem(15); + width: rem(15); + } + } + font-size: $small-font-size; + } + } + + .data-dictionary__table-wrapper { + @import './_dictionaryTable'; + } + @import './_searchBar'; + } + } +} diff --git a/src/_scss/pages/search/results/visualizations/geo/geoVisualization.scss b/src/_scss/pages/search/results/visualizations/geo/geoVisualization.scss index 21a37854de..887d7af851 100644 --- a/src/_scss/pages/search/results/visualizations/geo/geoVisualization.scss +++ b/src/_scss/pages/search/results/visualizations/geo/geoVisualization.scss @@ -22,4 +22,8 @@ @import "./_disclaimer"; @import "./_message"; + @import 'components/Note'; + .default-note { + padding-top: 0; + } } diff --git a/src/data/data-limitations.pdf b/src/data/data-limitations.pdf index 29a2e4b3d9..3332c2f9ae 100644 Binary files a/src/data/data-limitations.pdf and b/src/data/data-limitations.pdf differ diff --git a/src/js/helpers/agencyV2Helper.js b/src/js/apis/agencyV2APIs.js similarity index 80% rename from src/js/helpers/agencyV2Helper.js rename to src/js/apis/agencyV2APIs.js index 57d318b0b9..6cff5c76fc 100644 --- a/src/js/helpers/agencyV2Helper.js +++ b/src/js/apis/agencyV2APIs.js @@ -1,9 +1,9 @@ /** - * agencyV2Helper.js + * agencyV2APIs.js * Created by Lizzie Salita 5/26/20 */ -import { apiRequest } from './apiRequest'; +import { apiRequest } from '../helpers/apiRequest'; export const fetchSpendingCount = (agencyId, fy, type) => apiRequest({ url: `v2/agency/${agencyId}/${type}/count/`, @@ -18,7 +18,7 @@ export const fetchSpendingByCategory = (agencyId, type, params) => apiRequest({ }); export const fetchBudgetaryResources = (agencyId) => apiRequest({ - url: `v2/agency/${agencyId}/budgetary_resources/?fiscal_year=2020` + url: `v2/agency/${agencyId}/budgetary_resources/` }); export const fetchAgencyOverview = (code, fy) => apiRequest({ diff --git a/src/js/apis/dataDictionary.js b/src/js/apis/dataDictionary.js new file mode 100644 index 0000000000..54d1f1f4cf --- /dev/null +++ b/src/js/apis/dataDictionary.js @@ -0,0 +1,5 @@ +import { apiRequest } from '../helpers/apiRequest'; + +export const fetchDataDictionary = () => apiRequest({ + url: 'v2/references/data_dictionary/' +}); diff --git a/src/js/components/about/MoreInfo.jsx b/src/js/components/about/MoreInfo.jsx index 840f23c2a6..c777f16416 100644 --- a/src/js/components/about/MoreInfo.jsx +++ b/src/js/components/about/MoreInfo.jsx @@ -24,7 +24,7 @@ const MoreInfo = () => ( FAQs  and the  - + Data Dictionary . diff --git a/src/js/components/aboutTheData/AboutTheDataPage.jsx b/src/js/components/aboutTheData/AboutTheDataPage.jsx index 0145d5c1b5..5cc3fab7fc 100644 --- a/src/js/components/aboutTheData/AboutTheDataPage.jsx +++ b/src/js/components/aboutTheData/AboutTheDataPage.jsx @@ -11,9 +11,11 @@ import { Link, useLocation } from "react-router-dom"; import Header from "containers/shared/HeaderContainer"; import Footer from "containers/Footer"; import { getAllAgenciesEmail } from "helpers/aboutTheDataHelper"; +import { aboutTheDataMetaTags } from 'helpers/metaTagHelper'; import { getBaseUrl, handleShareOptionClick } from 'helpers/socialShare'; import { getStickyBreakPointForSidebar } from 'helpers/stickyHeaderHelper'; +import MetaTags from 'components/sharedComponents/metaTags/MetaTags'; import AboutTheDataModal from "components/aboutTheData/AboutTheDataModal"; import { LoadingWrapper } from "components/sharedComponents/Loading"; import AgenciesContainer from 'containers/aboutTheData/AgenciesContainer'; @@ -72,6 +74,7 @@ const AboutTheDataPage = ({ history }) => { return (
+
{ on a quarterly and/or monthly basis to USAspending.gov. The table below shows information about the status and content of these submissions. It will be updated as agencies publish/certify new submissions or - republish/recertify existing submissions. For more information about the data in this table, visit the Data Sources and Methodology page. + republish/recertify existing submissions. For more information about the data in this table, visit the Data Sources and Methodology page.

diff --git a/src/js/components/aboutTheData/AgencyDetailsPage.jsx b/src/js/components/aboutTheData/AgencyDetailsPage.jsx index 1ed647cedb..7e4cfd5bf5 100644 --- a/src/js/components/aboutTheData/AgencyDetailsPage.jsx +++ b/src/js/components/aboutTheData/AgencyDetailsPage.jsx @@ -7,8 +7,8 @@ import { useParams, Link } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { LoadingMessage, ErrorMessage, PageHeader } from 'data-transparency-ui'; -import { agencyPageMetaTags } from 'helpers/metaTagHelper'; -import { fetchAgencyOverview } from 'helpers/agencyV2Helper'; +import { aboutTheDataAgencyDetails } from 'helpers/metaTagHelper'; +import { fetchAgencyOverview } from 'apis/agencyV2APIs'; import { getAgencyDetailEmail } from 'helpers/aboutTheDataHelper'; import { getBaseUrl, handleShareOptionClick } from 'helpers/socialShare'; import { getStickyBreakPointForSidebar } from 'helpers/stickyHeaderHelper'; @@ -86,7 +86,7 @@ const AgencyDetailsPage = () => { return (
- + {agencyOverview && }
{
 Back to All Agencies diff --git a/src/js/components/aboutTheData/DataSourcesAndMethodologiesPage.jsx b/src/js/components/aboutTheData/DataSourcesAndMethodologiesPage.jsx index f7d17154e1..014927520c 100644 --- a/src/js/components/aboutTheData/DataSourcesAndMethodologiesPage.jsx +++ b/src/js/components/aboutTheData/DataSourcesAndMethodologiesPage.jsx @@ -150,7 +150,7 @@ const DataSourcesAndMethodologiesPage = () => {

Calculation:

- The percentages are based on the total budgetary resources for all TAS submitted in File A that matches the Agency Identifier (AID). This can be replicated with total_budgetary_resources field in the Account Balances (File A) file in Custom Account Download. + The percentages are based on the total budgetary resources for all TAS submitted in File A that matches the Agency Identifier (AID) divided by the total budgetary resources reported in GTAS (Line 1910 of the SF-133).

diff --git a/src/js/components/aboutTheData/componentMapping/tooltipContentMapping.jsx b/src/js/components/aboutTheData/componentMapping/tooltipContentMapping.jsx index 4a10a220e1..9da17bd413 100644 --- a/src/js/components/aboutTheData/componentMapping/tooltipContentMapping.jsx +++ b/src/js/components/aboutTheData/componentMapping/tooltipContentMapping.jsx @@ -6,19 +6,7 @@ import React from 'react'; export const tabTooltips = { - 'Updates by Fiscal Year': ( - <> -

- Please note that columns for the first and second period do not show data for agencies that are only required to report quarterly data. -

-

- The columns for the last period of each quarter (i.e., P03, P06, P09, P12) do show data for all agencies. Fiscal years start in October (Period 1), and starting in FY 2022 (i.e., October 2021), all agencies will report monthly data to USAspending.gov. -

-

- Cells with a “Not Certified” badge signal that an agency uploaded data but it is still under review. -

- - ), + 'Updates by Fiscal Year': (

The columns for the last period of each quarter (i.e., P03, P06, P09, P12) do show data for all agencies. Fiscal years start in October (Period 1), and starting in FY 2022 (i.e., October 2021), all agencies will report monthly data to USAspending.gov.

), 'Statistics by Submission Period': ( <>

@@ -41,7 +29,7 @@ export const columnTooltips = { If you are viewing the last period in a quarter, you may notice agencies that report quarterly do not show data, or have a later report date than the agencies reporting monthly. This is because the quarterly submission deadline is slightly later than the monthly deadline. Such timing differences will disappear in October 2021 when all agencies transition to monthly reporting.

- If you see a “Not Certified” badge, it means that an agency uploaded data but it is still under review. + "--" indicates that an agency has not submitted data for this period.

), @@ -53,6 +41,9 @@ export const columnTooltips = {

Note that financing TAS, while present in GTAS, are completely excluded from this calculation, as they do not involve budgetary spending and therefore are not appropriate for publication on USAspending.

+

+ "--" indicates that an agency has not submitted data for this period. +

), 'Reporting Difference in Obligations': ( @@ -63,6 +54,9 @@ export const columnTooltips = {

This column shows the differences in these two reported spending amounts.

+

+ "--" indicates that an agency has not submitted data for this period. +

), 'Number of Unlinked Contract Awards': ( @@ -101,5 +95,25 @@ export const columnTooltips = {

Agency Comments are optional and provided by agencies at the time they submit their data to USAspending.gov in the required dataset formats (File A, B, C, D1, and D2). For more information about the DATA Act reporting flow, visit https://fiscal.treasury.gov/files/data-transparency/daims-information-flow-diagram.pdf

+ ), + percentOfBudgetSubmissions: ( + <> +

+ This is an agency's total budgetary resources for the fiscal year through the selected period as a portion of all agency budgetary resources to-date. +

+

+ "--" indicates that an agency has not submitted data for this period. +

+ + ), + percentOfBudgetPublications: ( + <> +

+ This is an agency's total budgetary resources for the most recent period of the selected fiscal year as a portion of all agency budgetary resources to-date. +

+

+ "--" indicates that an agency has not submitted data for this period. +

+ ) }; diff --git a/src/js/components/agencyV2/AgencyPage.jsx b/src/js/components/agencyV2/AgencyPage.jsx index 5e2598d8cd..0c59660097 100644 --- a/src/js/components/agencyV2/AgencyPage.jsx +++ b/src/js/components/agencyV2/AgencyPage.jsx @@ -23,7 +23,7 @@ import Sidebar from 'components/sharedComponents/sidebar/Sidebar'; import Footer from 'containers/Footer'; import AccountSpending from 'components/agencyV2/accountSpending/AccountSpending'; import AgencySection from './AgencySection'; -import AgencyOverview from './AgencyOverview'; +import AgencyOverview from './overview/AgencyOverview'; require('pages/agencyV2/index.scss'); @@ -55,7 +55,7 @@ export const AgencyProfileV2 = ({ { name: 'overview', display: 'Overview', - component: + component: }, { name: 'budget-category', diff --git a/src/js/components/agencyV2/accountSpending/AccountSpending.jsx b/src/js/components/agencyV2/accountSpending/AccountSpending.jsx index 2379ef7b12..d019569eae 100644 --- a/src/js/components/agencyV2/accountSpending/AccountSpending.jsx +++ b/src/js/components/agencyV2/accountSpending/AccountSpending.jsx @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import { useDispatch } from 'react-redux'; import { setBudgetaryResources } from 'redux/actions/agencyV2/agencyV2Actions'; -import { fetchBudgetaryResources } from 'helpers/agencyV2Helper'; +import { fetchBudgetaryResources } from 'apis/agencyV2APIs'; import BaseAgencyBudgetaryResources from 'models/v2/agency/BaseAgencyBudgetaryResources'; import CountTabContainer from 'containers/agencyV2/accountSpending/CountTabContainer'; diff --git a/src/js/components/agencyV2/AgencyOverview.jsx b/src/js/components/agencyV2/overview/AgencyOverview.jsx similarity index 83% rename from src/js/components/agencyV2/AgencyOverview.jsx rename to src/js/components/agencyV2/overview/AgencyOverview.jsx index ab225d4809..11c8fb569c 100644 --- a/src/js/components/agencyV2/AgencyOverview.jsx +++ b/src/js/components/agencyV2/overview/AgencyOverview.jsx @@ -13,12 +13,14 @@ import { LoadingMessage } from 'data-transparency-ui'; import { mediumScreen } from 'dataMapping/shared/mobileBreakpoints'; import ExternalLink from 'components/sharedComponents/ExternalLink'; import ReadMore from 'components/sharedComponents/ReadMore'; +import FySummary from './FySummary'; const propTypes = { - isLoading: PropTypes.bool + isLoading: PropTypes.bool, + fy: PropTypes.string }; -const AgencyOverview = ({ isLoading }) => { +const AgencyOverview = ({ isLoading, fy }) => { const { name, logo, @@ -110,19 +112,25 @@ const AgencyOverview = ({ isLoading }) => { ); } - return isLoading ? : - ( -
-
-
-

{name}

-
Includes {subtierCount} awarding sub-agencies
-
- {image} + const overview = isLoading ? : ( + <> +
+
+

{name}

+
Includes {subtierCount} awarding sub-agencies
- {content} + {image}
- ); + {content} + + ); + + return ( +
+ {overview} + +
+ ); }; AgencyOverview.propTypes = propTypes; diff --git a/src/js/components/agencyV2/overview/FySummary.jsx b/src/js/components/agencyV2/overview/FySummary.jsx new file mode 100644 index 0000000000..63f391b51a --- /dev/null +++ b/src/js/components/agencyV2/overview/FySummary.jsx @@ -0,0 +1,90 @@ +/** + * FySummary.jsx + * Created by Lizzie Salita 4/7/21 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { ComingSoon, Carousel } from 'data-transparency-ui'; +import TotalObligationsOverTimeContainer from 'containers/agencyV2/visualizations/TotalObligationsOverTimeContainer'; +import VisualizationSection from './VisualizationSection'; + + +const propTypes = { + isMobile: PropTypes.bool, + fy: PropTypes.string +}; + +const FySummary = ({ isMobile, fy }) => { + // TODO eventually get this data via props or redux + const totalBudgetaryResources = '$1.42 Trillion'; + const percentOfFederalBudget = '15.5%'; + const totalObligations = '$1.11 Trillion'; + const percentOfBudgetaryResources = '79.1%'; + const awardObligations = '$10.62 Billion'; + const percentOfTotalObligations = '9.4%'; + const numberOfRecipients = '200'; + const percentOfFederalRecipients = '1.5%'; + + const components = [ + ( + How much can
this agency spend?)} + data={totalBudgetaryResources} + secondaryData={`${percentOfFederalBudget} of the FY ${fy} U.S. federal budget`} + label="Total Budgetary Resources Over Time" > + +
+ ), + ( + How much has this agency
spent in total?)} + data={totalObligations} + secondaryData={`${percentOfBudgetaryResources} of total budgetary resources`} + label="Total Obligations Over Time" > + + {/* */} +
+ ), + ( + How much has this agency
spent on awards?)} + data={awardObligations} + secondaryData={`${percentOfTotalObligations} of total obligations`} + label="Award Obligations by Type" > + +
+ ), + ( + How many award recipients
did this agency have?)} + data={numberOfRecipients} + secondaryData={`${percentOfFederalRecipients} of all federal recipients`} + label="Recipient Award Amount Distribution" > + +
+ ) + ]; + const content = isMobile ? ( + + ) : ( +
+ {components.map((viz, i) => ( +
+ {viz} +
+ ))} +
+ ); + + return ( +
+

FY {fy} Summary

+
+ {content} +
+ ); +}; + +FySummary.propTypes = propTypes; +export default FySummary; diff --git a/src/js/components/agencyV2/overview/VisualizationSection.jsx b/src/js/components/agencyV2/overview/VisualizationSection.jsx new file mode 100644 index 0000000000..54e5015828 --- /dev/null +++ b/src/js/components/agencyV2/overview/VisualizationSection.jsx @@ -0,0 +1,36 @@ +/** + * VisualizationSection.jsx + * Created by Lizzie Salita 4/7/21 + */ + +import React from 'react'; +import PropTypes, { oneOfType } from 'prop-types'; + +const propTypes = { + children: PropTypes.element, + subtitle: oneOfType([PropTypes.string, PropTypes.element]), + data: PropTypes.string, + secondaryData: PropTypes.string, + label: PropTypes.string +}; + +const VisualizationSection = ({ + children, + subtitle, + data, + secondaryData, + label +}) => ( +
+
{subtitle}
+
{data}
+
{secondaryData}
+
+ {children} +
+
{label}
+
+); + +VisualizationSection.propTypes = propTypes; +export default VisualizationSection; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/TotalObligationsOverTimeVisualization.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/TotalObligationsOverTimeVisualization.jsx new file mode 100644 index 0000000000..2b43e8e9d5 --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/TotalObligationsOverTimeVisualization.jsx @@ -0,0 +1,128 @@ +/** + * TotalObligationsOverTimeVisualization.jsx + * Created by Jonathan Hill 04/08/21 + */ + +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { scaleLinear } from 'd3-scale'; +import { getYear } from 'date-fns'; + +import { getYDomain, getMilliseconds } from 'helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper'; +import { xLabelHeightPlusPadding, yOffsetForPathStrokeWidth, defaultPadding } from 'dataMapping/agencyV2/visualizations/totalObligationsOverTime'; +import Paths from 'components/agencyV2/visualizations/totalObligationsOverTime/paths/Paths'; +import Axis from './axis/Axis'; + +const propTypes = { + height: PropTypes.number, + width: PropTypes.number, + padding: PropTypes.shape({ + left: PropTypes.number, + right: PropTypes.number, + bottom: PropTypes.number, + top: PropTypes.number + }), + data: PropTypes.arrayOf(PropTypes.shape({ + period: PropTypes.number, + obligated: PropTypes.number + })), + fy: PropTypes.string +}; + +const TotalObligationsOverTimeVisualization = ({ + height = 168, + width = 0, + padding = defaultPadding, + data = [], + fy = getYear(new Date(Date.now())) +}) => { + const [xDomain, setXDomain] = useState([]); + const [yDomain, setYDomain] = useState([]); + const [xScale, setXScale] = useState(null); + const [xScaleForPath, setXScaleForPath] = useState(null); + const [yScale, setYScale] = useState(null); + const [yScaleForPath, setYScaleForPath] = useState(null); + const [xTicks, setXTicks] = useState([]); + const [dataWithFirstCoordinate, setDataWithFirstCoordinate] = useState([]); + // x domain + useEffect(() => { + // start of the domain is October 1st of the prior selected fiscal year midnight local time + const start = new Date(parseInt(fy, 10) - 1, 9, 1); + // end of the domain is September 30th midnight local time + const end = new Date(`${fy}`, 8, 30); + setXDomain([getMilliseconds(start), getMilliseconds(end)]); + }, [fy]); + // add first data point as start of graph + useEffect(() => { + if (xDomain.length && data.length) { + setDataWithFirstCoordinate([{ endDate: xDomain[0], obligated: 0 }, ...data]); + } + }, [xDomain, data]); + // y domain + useEffect(() => setYDomain(getYDomain(data)), [data]); + /** + * set x scale + * - The range max value removes padding left and right since that is padding for the + * x-axis labels overflowing outside of the graph based on the mock + * and not going to be part of the graphable width. + */ + useEffect(() => { + setXScale(() => scaleLinear().domain(xDomain).range([0, width - padding.left - padding.right])); + setXScaleForPath(() => scaleLinear().domain(xDomain).range([0, width - padding.left - padding.right])); + }, + [xDomain, width]); + /** + * set y scale + * - The range max value removes padding top and bottom since that is padding for the top based on the mock and + * bottom x-axis labels and not going to be part of the graphable width. + */ + useEffect(() => { + setYScale(() => scaleLinear().domain(yDomain).range([0, height - padding.top - padding.bottom])); + setYScaleForPath(() => scaleLinear().domain(yDomain).range([1, height - padding.top - padding.bottom - yOffsetForPathStrokeWidth])); + }, [yDomain, data]); + + // set x ticks + useEffect(() => { + if (xScale) { + setXTicks([ + { + x: isNaN(xScale(xDomain[0])) ? 0 : xScale(xDomain[0]) + padding.left, + y: (height - padding.bottom - padding.top) + xLabelHeightPlusPadding, + label: `Oct FY${fy.substring(2)}` + }, + { + x: isNaN(xScale(xDomain[1])) ? 0 : xScale(xDomain[1]) + padding.left, + y: (height - padding.bottom - padding.top) + xLabelHeightPlusPadding, + label: `Sep FY${fy.substring(2)}` + } + ]); + } + }, [xScale, xDomain]); + + return ( + + + + + + + ); +}; + +TotalObligationsOverTimeVisualization.propTypes = propTypes; +export default TotalObligationsOverTimeVisualization; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/Axis.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/Axis.jsx new file mode 100644 index 0000000000..4f407cc1ed --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/Axis.jsx @@ -0,0 +1,53 @@ +/** + * Axis.jsx + * Created by Jonathan Hill 04/09/21 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import XAxis from './XAxis'; +import YAxis from './YAxis'; + +const propTypes = { + padding: PropTypes.shape({ + right: PropTypes.number, + left: PropTypes.number, + top: PropTypes.number, + bottom: PropTypes.number + }), + width: PropTypes.number, + height: PropTypes.number, + xTicks: PropTypes.array, + yTicks: PropTypes.array +}; + +const Axis = ({ + padding = { + right: 0, + left: 0, + top: 0, + bottom: 0 + }, + width = 0, + height = 0, + xTicks = [], + yTicks = [] +}) => ( + + + + +); + +Axis.propTypes = propTypes; +export default Axis; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/AxisLabel.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/AxisLabel.jsx new file mode 100644 index 0000000000..9f40dc3303 --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/AxisLabel.jsx @@ -0,0 +1,32 @@ +/** + * AxisLabel.jsx + * Created by Jonathan Hill 04/09/21 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + axis: PropTypes.string, + x: PropTypes.number, + y: PropTypes.number, + label: PropTypes.string, + index: PropTypes.number +}; + +const AxisLabel = ({ + axis = 'x', + x = 0, + y = 0, + label = '' +}) => ( + + {label} + +); + +AxisLabel.propTypes = propTypes; +export default AxisLabel; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/XAxis.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/XAxis.jsx new file mode 100644 index 0000000000..4ea7eab26b --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/XAxis.jsx @@ -0,0 +1,55 @@ +/** + * XAxis.jsx + * Created by Jonathan Hill 04/09/21 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import AxisLabel from './AxisLabel'; + +const propTypes = { + className: PropTypes.string, + ticks: PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string, + x: PropTypes.number, + y: PropTypes.number + })), + x1: PropTypes.number, + x2: PropTypes.number, + y1: PropTypes.number, + y2: PropTypes.number +}; + +const XAxis = ({ + className, + ticks, + x1, + x2, + y1, + y2 +}) => ( + + The X-Axis + The X-Axis consisting of a horizontal line and labels + + + The X-Axis Labels + { + ticks.map((tick, i) => ( + + )) + } + + +); + +XAxis.propTypes = propTypes; +export default XAxis; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/YAxis.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/YAxis.jsx new file mode 100644 index 0000000000..d26bd2e93f --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/axis/YAxis.jsx @@ -0,0 +1,55 @@ +/** + * YAxis.jsx + * Created by Jonathan Hill 04/09/21 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import AxisLabel from './AxisLabel'; + +const propTypes = { + className: PropTypes.string, + ticks: PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string, + x: PropTypes.number, + y: PropTypes.number + })), + x1: PropTypes.number, + x2: PropTypes.number, + y1: PropTypes.number, + y2: PropTypes.number +}; + +const YAxis = ({ + className, + ticks, + x1, + x2, + y1, + y2 +}) => ( + + The Y-Axis + The Y-Axis consisting of a vertical line and labels + + + { + ticks.map((tick, i) => ( + + )) + } + + +); + +YAxis.propTypes = propTypes; +export default YAxis; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/AreaPath.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/AreaPath.jsx new file mode 100644 index 0000000000..f252a7105b --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/AreaPath.jsx @@ -0,0 +1,63 @@ +/** + * AreaPath.jsx + * Created by Jonathan Hill 04/12/21 + */ + +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + classname: PropTypes.string, + data: PropTypes.array, + xScale: PropTypes.func, + yScale: PropTypes.func, + xProperty: PropTypes.string, + yProperty: PropTypes.string, + height: PropTypes.number, + width: PropTypes.number, + padding: PropTypes.shape({ + left: PropTypes.number, + right: PropTypes.number, + bottom: PropTypes.number, + top: PropTypes.number + }) +}; + +const AreaPath = ({ + classname = "", + data = [], + xScale = () => {}, + yScale = () => {}, + xProperty = 'endDate', + yProperty = 'obligated', + height = 0, + padding = { + top: 0, + bottom: 0, + right: 0, + left: 0 + } +}) => { + const [d, setD] = useState(''); + useEffect(() => { + if (xScale && yScale) { + setD(data.reduce((path, currentItem, i, originalArray) => { + if (i === 0) { + const updatedPath = `${path}${xScale(currentItem[xProperty]) + padding.left},${height - yScale(currentItem[yProperty]) - padding.top - padding.bottom}`; + return updatedPath; + } + if (originalArray.length === i + 1) { + const updatedPath = `${path}L${xScale(currentItem[xProperty]) + padding.left},${height - yScale(currentItem[yProperty]) - padding.top - padding.bottom}L${xScale(currentItem[xProperty]) + padding.left},${height - padding.bottom - padding.top}Z`; + return updatedPath; + } + const updatedPath = `${path}L${xScale(currentItem[xProperty]) + padding.left},${height - yScale(currentItem[yProperty]) - padding.top - padding.bottom}`; + return updatedPath; + }, 'M')); + } + }, [data, xScale, yScale]); + + return (); +}; + +AreaPath.propTypes = propTypes; +export default AreaPath; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/Path.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/Path.jsx new file mode 100644 index 0000000000..bd6510ae85 --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/Path.jsx @@ -0,0 +1,57 @@ +/** + * Path.jsx + * Created by Jonathan Hill 04/13/21 + */ + +import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; + +const propTypes = { + data: PropTypes.array, + xScale: PropTypes.func, + yScale: PropTypes.func, + xProperty: PropTypes.string, + yProperty: PropTypes.string, + height: PropTypes.number, + padding: PropTypes.shape({ + left: PropTypes.number, + right: PropTypes.number, + bottom: PropTypes.number, + top: PropTypes.number + }) +}; + +const Path = ({ + data = [], + xScale = () => {}, + yScale = () => {}, + xProperty = 'endDate', + yProperty = 'obligated', + height = 0, + padding = { + top: 0, + bottom: 0, + right: 0, + left: 0 + } +}) => { + const [d, setD] = useState(''); + + useEffect(() => { + if (xScale && yScale) { + setD(data.reduce((path, currentItem, i) => { + if (i === 0) { + const updatedPath = `${path}${xScale(currentItem[xProperty]) + padding.left},${height - yScale(currentItem[yProperty]) - padding.top - padding.bottom}`; + return updatedPath; + } + const updatedPath = `${path}L${xScale(currentItem[xProperty]) + padding.left},${height - yScale(currentItem[yProperty]) - padding.top - padding.bottom}`; + return updatedPath; + }, 'M')); + } + }, [data, xScale, yScale]); + + return ; +}; + +Path.propTypes = propTypes; +export default Path; diff --git a/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/Paths.jsx b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/Paths.jsx new file mode 100644 index 0000000000..a771a175bb --- /dev/null +++ b/src/js/components/agencyV2/visualizations/totalObligationsOverTime/paths/Paths.jsx @@ -0,0 +1,62 @@ +/** + * Paths.jsx + * Created by Jonathan Hill 04/13/21 + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import Path from './Path'; +import AreaPath from './AreaPath'; + +const propTypes = { + data: PropTypes.array, + xScale: PropTypes.func, + xScaleForPath: PropTypes.func, + yScale: PropTypes.func, + yScaleForPath: PropTypes.func, + xProperty: PropTypes.string, + yProperty: PropTypes.string, + height: PropTypes.number, + width: PropTypes.number, + padding: PropTypes.shape({ + left: PropTypes.number, + right: PropTypes.number, + bottom: PropTypes.number, + top: PropTypes.number + }) +}; + +const Paths = ({ + data = [], + xScale = () => {}, + xScaleForPath = () => {}, + yScale = () => {}, + yScaleForPath = () => {}, + height = 0, + width = 0, + padding = { + top: 0, + bottom: 0, + right: 0, + left: 0 + } +}) => ( + + + a.endDate - b.endDate)} + xScale={xScaleForPath} + yScale={yScaleForPath} + height={height} + padding={padding} /> + +); + +Paths.propTypes = propTypes; +export default Paths; diff --git a/src/js/components/bulkDownload/BulkDownloadPage.jsx b/src/js/components/bulkDownload/BulkDownloadPage.jsx index ef51d57d0a..bf15e3641a 100644 --- a/src/js/components/bulkDownload/BulkDownloadPage.jsx +++ b/src/js/components/bulkDownload/BulkDownloadPage.jsx @@ -11,7 +11,6 @@ import { downloadArchivePageMetaTags, downloadAwardPageMetaTags, downloadAccountPageMetaTags, - dataDictionaryPageMetaTags, metadataDownloadPageMetaTags } from 'helpers/metaTagHelper'; import { getStickyBreakPointForSidebar } from 'helpers/stickyHeaderHelper'; @@ -19,15 +18,12 @@ import { getStickyBreakPointForSidebar } from 'helpers/stickyHeaderHelper'; import Footer from 'containers/Footer'; import Header from 'containers/shared/HeaderContainer'; - import MetaTags from 'components/sharedComponents/metaTags/MetaTags'; import MetadataDownload from 'components/bulkDownload/MetadataDownload'; import AwardDataContainer from 'containers/bulkDownload/awards/AwardDataContainer'; import AccountDataContainer from 'containers/bulkDownload/accounts/AccountDataContainer'; import AwardDataArchiveContainer from 'containers/bulkDownload/archive/AwardDataArchiveContainer'; -import BulkDownloadModalContainer from - 'containers/bulkDownload/modal/BulkDownloadModalContainer'; -import DataDictionaryContainer from 'containers/bulkDownload/dictionary/DataDictionaryContainer'; +import BulkDownloadModalContainer from 'containers/bulkDownload/modal/BulkDownloadModalContainer'; import BulkDownloadSidebar from './sidebar/BulkDownloadSidebar'; const propTypes = { @@ -39,7 +35,6 @@ const propTypes = { }; const metaTagsByDataType = { - data_dictionary: dataDictionaryPageMetaTags, awards: downloadAwardPageMetaTags, accounts: downloadAccountPageMetaTags, award_data_archive: downloadArchivePageMetaTags, @@ -106,11 +101,6 @@ export default class BulkDownloadPage extends React.Component { clickedDownload={this.clickedDownload} /> ); } - if (this.props.dataType === 'data_dictionary') { - downloadDataContent = ( - - ); - } if (this.props.dataType === 'dataset_metadata') { downloadDataContent = ( diff --git a/src/js/components/covid19/AwardQuestions.jsx b/src/js/components/covid19/AwardQuestions.jsx index 5216efa290..31514b231b 100644 --- a/src/js/components/covid19/AwardQuestions.jsx +++ b/src/js/components/covid19/AwardQuestions.jsx @@ -15,8 +15,8 @@ const AwardQuestion = () => (

- Award spending is a subset of total spending and refers to money given through contracts or financial assistance to individuals, organizations, businesses, or state, local, or tribal governments. - There are two main categories of awards: contracts and financial assistance. Loan spending is a type of financial assistance with two components: face value and subsidy cost . + Award spending is a subset of total spending and refers to money given through contracts or financial assistance to individuals, organizations, businesses, or state, local, or tribal governments. + There are two main categories of awards: contracts and financial assistance. Loan spending is a type of financial assistance with two components: face value and subsidy cost .

diff --git a/src/js/components/covid19/DataSourcesAndMethodologiesPage.jsx b/src/js/components/covid19/DataSourcesAndMethodologiesPage.jsx index 64b0c250d7..04f93a76c3 100644 --- a/src/js/components/covid19/DataSourcesAndMethodologiesPage.jsx +++ b/src/js/components/covid19/DataSourcesAndMethodologiesPage.jsx @@ -33,6 +33,10 @@ const getEmailSocialShareData = { }; const sections = [ + { + label: 'What COVID-19 spending does USAspending track?', + section: 'covered_funds' + }, { label: 'Datasets', section: 'datasets' @@ -193,7 +197,7 @@ export default () => {

-
+
{ Back to the COVID-19 Spending profile
+

+ What COVID-19 spending does USAspending track? +

+
+

+ Congress introduced and defined the concept of "covered funds" in the + CARES Act to make it clear which supplemental appropriation spending related to + the coronavirus response they wanted to be tracked, audited, and published for + transparency and accountability purposes. The act (as amended) defines covered funds + as follows: +

+
    +
  • + (6) the term "covered funds" means any funds, including loans, that are made available in any form to any non-Federal entity, not including an individual, under— +
      +
    • + (A) the Coronavirus Aid, Relief, and Economic Security Act (divisions A and B); +
    • +
    • + (B) the Coronavirus Preparedness and Response Supplemental Appropriations Act, 2020 (Public Law 116–123); +
    • +
    • + (C) the Families First Coronavirus Response Act (Public Law 116–127); +
    • +
    • + (D) the Paycheck Protection Program and Health Care Enhancement Act (Public Law 116–139); or +
    • +
    • + (E) divisions M and N of the Consolidated Appropriations Act, 2021; +
    • +
    +
  • +
+

+ Given the above, OMB centered their guidance in M-20-21 on the covered funds concept and elected to use + the DEFC as the means to track these covered funds in USAspending. New DEFC (see below) were issued to + track each component of covered funds, consistent with the original and continued purpose of the DEFC to + track Disaster, Emergency, and Wildfire Suppression spending under BBEDCA; covered funds that fell outside + of the BBEDCA categories (and the specific statutory language that triggers their use) were captured in a  + Non-emergency DEFC, O. +

+

+ One result of the covered funds concept and M-20-21 is that some spending that is clearly associated with the coronavirus response is not + tracked as ‘COVID-19 spending’ on our site. Examples include: +

+
    +
  1. + Any spending funded from general appropriations (rather than the supplemental appropriations that constitute "covered funds"). Examples + include any contracts with a National Interest Action code (NIA) of "P20C" (indicating a COVID-19 related purpose) that are funded by + general appropriations only and not ‘covered funds’ and that therefore do not appear on this page. The NIA is assigned by contracting officers + based on spending purpose and has nothing to do with funding; in contrast, the CARES Act and M-20-21 are concerned with tracking spending + from specific funding sources and not based on purpose alone. The NIA has no direct bearing on whether a contract is considered ‘COVID-19 spending’ + on our site. +
  2. +
  3. + Any spending from entities that were appropriated covered funds from Congress but that do not report to USAspending under the DATA Act. M-20-21 was an expansion + of existing DATA Act reporting requirements but did not change the population of federal agencies or other entities subject to those requirements. Examples include + any component of the legislative and judicial branches (though GAO does voluntarily report). +
  4. +
  5. + Any spending from the Consolidated Appropriations Act, 2021 (PL 116-260) outside of Division M and N. Per the covered funds definition, Congress only + intended these two divisions to be tracked under the requirements of the CARES Act. An example that is arguably ‘coronavirus-related’; but is not + tracked and displayed as such on USAspending is the $635,000,000 appropriated in Division A "for necessary expenses for salary and related costs associated + with Agriculture Quarantine and Inspection Services [...] to offset the loss resulting from the coronavirus pandemic of quarantine and inspection fees." +
  6. +
+
+
+

Datasets

diff --git a/src/js/components/covid19/Heading.jsx b/src/js/components/covid19/Heading.jsx index 2a1512a81d..dbcc553d5c 100644 --- a/src/js/components/covid19/Heading.jsx +++ b/src/js/components/covid19/Heading.jsx @@ -24,7 +24,7 @@ const Heading = () => {

In early 2020, the U.S. Congress appropriated funds in response to the COVID-19 pandemic. These funds were made possible through the Coronavirus Aid, Relief, and Economic Security (CARES) Act and other supplemental legislation.

- In response to guidance from the Office of Management and Budget (OMB), we updated our data model to capture the journey of COVID-19 dollars from appropriation to obligation and outlay by federal agencies.1 2 See who received funding, which agencies spent the funds, which programs were funded, and more. + In response to guidance from the Office of Management and Budget (OMB), we updated our data model to capture the journey of COVID-19 dollars from appropriation to obligation and outlay by federal agencies.1 2 See who received funding, which agencies spent the funds, which programs were funded, and more.

Visit our and sections to learn more about the underlying data and find resources about COVID-19 from other agencies. diff --git a/src/js/components/covid19/assistanceListing/SpendingByCFDA.jsx b/src/js/components/covid19/assistanceListing/SpendingByCFDA.jsx index 978c35a916..aaa861e098 100644 --- a/src/js/components/covid19/assistanceListing/SpendingByCFDA.jsx +++ b/src/js/components/covid19/assistanceListing/SpendingByCFDA.jsx @@ -127,7 +127,7 @@ const SpendingByCFDA = () => {

- Catalog of Federal Domestic Assistance (CFDA) Programs , also known as Assistance Listings, are programs that provide financial assistance to individuals, organizations, businesses, or state, local, or tribal governments. Some examples of Assistance Listings include the Supplemental Nutrition Assistance Program (SNAP) and the Coronavirus Relief Fund. All financial assistance awards must be associated with a CFDA Program, all of which must be explicitly authorized by law. + Catalog of Federal Domestic Assistance (CFDA) Programs , also known as Assistance Listings, are programs that provide financial assistance to individuals, organizations, businesses, or state, local, or tribal governments. Some examples of Assistance Listings include the Supplemental Nutrition Assistance Program (SNAP) and the Coronavirus Relief Fund. All financial assistance awards must be associated with a CFDA Program, all of which must be explicitly authorized by law.

diff --git a/src/js/components/covid19/budgetCategories/BudgetCategories.jsx b/src/js/components/covid19/budgetCategories/BudgetCategories.jsx index edcdaf9523..f3cf10f3a4 100644 --- a/src/js/components/covid19/budgetCategories/BudgetCategories.jsx +++ b/src/js/components/covid19/budgetCategories/BudgetCategories.jsx @@ -113,7 +113,7 @@ const BudgetCategories = () => {

How is total COVID-19 spending categorized?

- In this section, we present the total amount of COVID-19 funding divided into three high-level budget categories: the Agencies who are authorizing the funds to be spent; the Federal Accounts from which agencies authorize spending; and the Object Classes of the goods and services purchased with this funding. + In this section, we present the total amount of COVID-19 funding divided into three high-level budget categories: the Agencies who are authorizing the funds to be spent; the Federal Accounts from which agencies authorize spending; and the Object Classes of the goods and services purchased with this funding.

{kGlobalConstants.CARES_ACT_RELEASED_2 && ( diff --git a/src/js/components/bulkDownload/dictionary/DataDictionary.jsx b/src/js/components/dataDictionary/DataDictionary.jsx similarity index 97% rename from src/js/components/bulkDownload/dictionary/DataDictionary.jsx rename to src/js/components/dataDictionary/DataDictionary.jsx index 7d39ce1ab8..983a24f651 100644 --- a/src/js/components/bulkDownload/dictionary/DataDictionary.jsx +++ b/src/js/components/dataDictionary/DataDictionary.jsx @@ -9,7 +9,7 @@ import PropTypes from 'prop-types'; import Analytics from 'helpers/analytics/Analytics'; import { Spreadsheet } from 'components/sharedComponents/icons/Icons'; import DataDictionaryTable from './table/DataDictionaryTable'; -import DataDictionarySearchBar from "./DataDictionarySearchBar"; +import DataDictionarySearchBar from './DataDictionarySearchBar'; const propTypes = { loading: PropTypes.bool, diff --git a/src/js/components/dataDictionary/DataDictionaryPage.jsx b/src/js/components/dataDictionary/DataDictionaryPage.jsx new file mode 100644 index 0000000000..e91c1c7cce --- /dev/null +++ b/src/js/components/dataDictionary/DataDictionaryPage.jsx @@ -0,0 +1,35 @@ +/** + * DataDictionaryPage.jsx + * Created by Brett Varney 4/22/2021 + */ + +import React from 'react'; +import { PageHeader } from "data-transparency-ui"; + +import { dataDictionaryPageMetaTags } from 'helpers/metaTagHelper'; +import MetaTags from 'components/sharedComponents/metaTags/MetaTags'; +import Header from 'containers/shared/HeaderContainer'; +import Footer from "containers/Footer"; +import { getStickyBreakPointForSidebar } from 'helpers/stickyHeaderHelper'; + +import DataDictionaryContainer from 'containers/dataDictionary/DataDictionaryContainer'; + +require('pages/dataDictionary/dataDictionaryPage.scss'); + +export default class DataDictionaryPage extends React.Component { + render = () => ( +
+ +
+ +
+ +
+
+ +
+ ); +} diff --git a/src/js/components/bulkDownload/dictionary/DataDictionarySearchBar.jsx b/src/js/components/dataDictionary/DataDictionarySearchBar.jsx similarity index 100% rename from src/js/components/bulkDownload/dictionary/DataDictionarySearchBar.jsx rename to src/js/components/dataDictionary/DataDictionarySearchBar.jsx diff --git a/src/js/components/bulkDownload/dictionary/table/DataDictionaryTable.jsx b/src/js/components/dataDictionary/table/DataDictionaryTable.jsx similarity index 100% rename from src/js/components/bulkDownload/dictionary/table/DataDictionaryTable.jsx rename to src/js/components/dataDictionary/table/DataDictionaryTable.jsx diff --git a/src/js/components/bulkDownload/dictionary/table/DataDictionaryTableSorter.jsx b/src/js/components/dataDictionary/table/DataDictionaryTableSorter.jsx similarity index 100% rename from src/js/components/bulkDownload/dictionary/table/DataDictionaryTableSorter.jsx rename to src/js/components/dataDictionary/table/DataDictionaryTableSorter.jsx diff --git a/src/js/components/search/visualizations/geo/GeoVisualizationSection.jsx b/src/js/components/search/visualizations/geo/GeoVisualizationSection.jsx index f6383b0d7d..5b06de327f 100644 --- a/src/js/components/search/visualizations/geo/GeoVisualizationSection.jsx +++ b/src/js/components/search/visualizations/geo/GeoVisualizationSection.jsx @@ -11,6 +11,8 @@ import MapboxGL from 'mapbox-gl/dist/mapbox-gl'; import ResultsTableErrorMessage from 'components/search/table/ResultsTableErrorMessage'; import LoadingSpinner from 'components/sharedComponents/LoadingSpinner'; import { ExclamationTriangle } from 'components/sharedComponents/icons/Icons'; +import Note from 'components/sharedComponents/Note'; +import { noteMessage } from 'dataMapping/search/geoVisualizationSection'; import GeoVisualizationScopeButton from './GeoVisualizationScopeButton'; import MapWrapper from './MapWrapper'; @@ -211,6 +213,7 @@ export default class GeoVisualizationSection extends React.Component { {disclaimer} {message} + ); } diff --git a/src/js/components/sharedComponents/GlossaryLink.jsx b/src/js/components/sharedComponents/GlossaryLink.jsx index e4677e1c2f..66c8343d45 100644 --- a/src/js/components/sharedComponents/GlossaryLink.jsx +++ b/src/js/components/sharedComponents/GlossaryLink.jsx @@ -5,19 +5,25 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { Link, useLocation } from 'react-router-dom'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Link } from 'react-router-dom'; + +import { getNewUrlForGlossary } from 'helpers/glossaryHelper'; const propTypes = { term: PropTypes.string.isRequired, currentUrl: PropTypes.string.isRequired }; -const GlossaryLink = ({ term, currentUrl }) => ( - - - -); +const GlossaryLink = ({ term }) => { + const { pathname, search } = useLocation(); + const newUrl = getNewUrlForGlossary(pathname, `/?glossary=${term}`, search); + return ( + + + + ); +}; GlossaryLink.propTypes = propTypes; export default GlossaryLink; diff --git a/src/js/components/sharedComponents/header/NavBar.jsx b/src/js/components/sharedComponents/header/NavBar.jsx index a74e1fb4a2..e52db1610c 100644 --- a/src/js/components/sharedComponents/header/NavBar.jsx +++ b/src/js/components/sharedComponents/header/NavBar.jsx @@ -6,14 +6,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEnvelope } from "@fortawesome/free-regular-svg-icons"; import Analytics from 'helpers/analytics/Analytics'; -import GlossaryButtonWrapperContainer from 'containers/glossary/GlossaryButtonWrapperContainer'; import { searchOptions, profileOptions, downloadGlobalNavigationOptions, resourceOptions } from 'dataMapping/navigation/menuOptions'; import EmailSignUp from 'components/homepage/EmailSignUp'; -import { DEV, QAT, STAGING } from '../../../GlobalConstants'; +import { DEV } from '../../../GlobalConstants'; import Dropdown from './Dropdown'; import MobileNav from './mobile/MobileNav'; -import NavBarGlossaryLink from './NavBarGlossaryLink'; const clickedHeaderLink = (route) => { Analytics.event({ @@ -22,8 +20,6 @@ const clickedHeaderLink = (route) => { }); }; -const isNotProd = (DEV || QAT || STAGING); - export default class NavBar extends React.Component { constructor(props) { super(props); @@ -186,23 +182,18 @@ export default class NavBar extends React.Component { className="full-menu__item" role="menuitem"> - {isNotProd && ( -
  • - -
  • - )} - {!isNotProd && ( - - )} +
  • + +
  • diff --git a/src/js/components/sharedComponents/header/mobile/MobileNav.jsx b/src/js/components/sharedComponents/header/mobile/MobileNav.jsx index ccafd487f3..c6d6cf62bc 100644 --- a/src/js/components/sharedComponents/header/mobile/MobileNav.jsx +++ b/src/js/components/sharedComponents/header/mobile/MobileNav.jsx @@ -8,14 +8,10 @@ import PropTypes from 'prop-types'; import { withRouter, Link } from 'react-router-dom'; import Analytics from 'helpers/analytics/Analytics'; -import { DEV, QAT, STAGING } from 'GlobalConstants'; - -import GlossaryButtonWrapperContainer from 'containers/glossary/GlossaryButtonWrapperContainer'; import { searchOptions, profileOptions, downloadGlobalNavigationOptions, resourceOptions } from 'dataMapping/navigation/menuOptions'; import MobileTop from './MobileTop'; -import MobileGlossaryButton from './MobileGlossaryButton'; import MobileDropdown from './MobileDropdown'; const clickedHeaderLink = (route) => { @@ -30,8 +26,6 @@ const propTypes = { location: PropTypes.object }; -const isNotProd = (DEV || QAT || STAGING); - export class MobileNav extends React.Component { constructor(props) { super(props); @@ -102,29 +96,19 @@ export class MobileNav extends React.Component {

  • - {isNotProd && ( -
  • - -
    -
  • - )} - {!isNotProd && ( -
  • - -
    -
  • - )} +
  • + +
    +
  • diff --git a/src/js/containers/Footer.jsx b/src/js/containers/Footer.jsx index b259af8ab9..2466f4d61b 100644 --- a/src/js/containers/Footer.jsx +++ b/src/js/containers/Footer.jsx @@ -9,7 +9,6 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faFacebookSquare, faLinkedin, faGithub, faTwitter } from "@fortawesome/free-brands-svg-icons"; -import { QAT, DEV, STAGING } from 'GlobalConstants'; import { showModal } from 'redux/actions/modal/modalActions'; @@ -35,8 +34,6 @@ const clickedFooterLink = (route) => { }); }; -const isNotProd = (DEV || QAT || STAGING); - const Footer = ({ filters, redirectUser @@ -116,35 +113,6 @@ const Footer = ({
    - {!isNotProd && ( -
    -
    - Resources -
    -
      -
    • - - Data Dictionary - -
    • -
    • - -
    • -
    • - -
    • -
    • - -
    • -
    -
    - )}
    Developers @@ -160,30 +128,26 @@ const Footer = ({ link="https://github.com/fedspendingtransparency/usaspending-website/tree/master" title="Explore the Code" /> - {isNotProd && ( -
  • - -
  • - )} +
  • + +
  • - {isNotProd && ( -
    -
    - Our Sites -
    - +
    +
    + Our Sites
    - )} + +
    • diff --git a/src/js/containers/aboutTheData/AgenciesContainer.jsx b/src/js/containers/aboutTheData/AgenciesContainer.jsx index ab346c4a36..c0de7cb201 100644 --- a/src/js/containers/aboutTheData/AgenciesContainer.jsx +++ b/src/js/containers/aboutTheData/AgenciesContainer.jsx @@ -1,18 +1,25 @@ import React, { useEffect, useRef, useState, useCallback } from 'react'; import PropTypes from 'prop-types'; import { Table, Pagination } from 'data-transparency-ui'; -import { throttle } from 'lodash'; +import { throttle, isNull } from 'lodash'; import { useDispatch, useSelector } from 'react-redux'; import DrilldownCell from 'components/aboutTheData/DrilldownCell'; import CellWithModal from 'components/aboutTheData/CellWithModal'; import { setTableData, setTableSort, setTotals, setSearchResults, setSearchTerm } from 'redux/actions/aboutTheData'; -import { getTotalBudgetaryResources, getAgenciesReportingData, getSubmissionPublicationDates, usePagination, isPeriodSelectable, getFederalBudget } from 'helpers/aboutTheDataHelper'; +import { + getTotalBudgetaryResources, + getAgenciesReportingData, + getSubmissionPublicationDates, + usePagination, + isPeriodSelectable, + getFederalBudget +} from 'helpers/aboutTheDataHelper'; import { getLatestPeriod } from 'helpers/accountHelper'; import BaseAgencyRow from 'models/v2/aboutTheData/BaseAgencyRow'; import PublicationOverviewRow from 'models/v2/aboutTheData/PublicationOverviewRow'; import AgencyDownloadLinkCell from 'components/aboutTheData/AgencyDownloadLinkCell'; -import { agenciesTableColumns } from './AgencyTableMapping'; +import { agenciesTableColumns, parsePeriods } from './AgencyTableMapping'; const propTypes = { openModal: PropTypes.func.isRequired, @@ -21,15 +28,6 @@ const propTypes = { selectedPeriod: PropTypes.string }; -const parsePeriods = (periods) => periods - .map(({ publicationDate, showNotCertified }) => ( -
      - {(publicationDate) && publicationDate} - {!publicationDate && "--"} - {showNotCertified && publicationDate && NOT CERTIFIED} -
      - )); - const AgenciesContainer = ({ activeTab, openModal, @@ -258,10 +256,13 @@ const AgenciesContainer = ({ mostRecentPublicationDate, _discrepancyCount, discrepancyCount: GtasNotInFileA, + _obligationDifference, obligationDifference, _gtasObligationTotal, percentageOfTotalFederalBudget, + _unlinkedContracts, unlinkedContracts, + _unlinkedAssistance, unlinkedAssistance, assuranceStatement }) => [ @@ -279,7 +280,7 @@ const AgenciesContainer = ({ fiscalYear: selectedFy, fiscalPeriod: selectedPeriod?.id }} />), - (_discrepancyCount === 0 ? + (isNull(_discrepancyCount) ?
      {GtasNotInFileA}
      : ), - (), - unlinkedContracts !== '0' ? () : (
      {unlinkedContracts}
      ), - unlinkedAssistance !== '0' ? () : (
      {unlinkedAssistance}
      ), + (isNull(_obligationDifference) ? +
      {obligationDifference}
      : + ), + (isNull(_unlinkedContracts) ? +
      {unlinkedContracts}
      : + ), + (isNull(_unlinkedAssistance) ? +
      {unlinkedAssistance}
      : + ), (
      ) ]); @@ -363,7 +370,7 @@ const AgenciesContainer = ({ )} {activeTab === 'publications' && ( { agencyName, agencyCode }} />, - rowData._discrepancyCount === 0 ? + isNull(rowData._discrepancyCount) ?
      {rowData.discrepancyCount}
      : { agencyCode, gtasObligationTotal: rowData._gtasObligationTotal }} />, - , - rowData._unlinkedContracts !== 0 ? ( + isNull(rowData._obligationDifference) ? +
      {rowData.obligationDifference}
      : + , + isNull(rowData._unlinkedContracts) ? +
      {rowData.unlinkedContracts}
      : { fiscalYear: rowData.fiscalYear, fiscalPeriod: rowData.fiscalPeriod, type: 'Contract' - }} /> - ) : ( -
      {rowData.unlinkedContracts}
      - ), - rowData._unlinkedAssistance !== 0 ? ( + }} />, + isNull(rowData._unlinkedAssistance) ? +
      {rowData.unlinkedAssistance}
      : { fiscalYear: rowData.fiscalYear, fiscalPeriod: rowData.fiscalPeriod, type: 'Assistance' - }} /> - ) : ( -
      {rowData.unlinkedAssistance}
      - ), + }} />,
      diff --git a/src/js/containers/aboutTheData/AgencyTableMapping.jsx b/src/js/containers/aboutTheData/AgencyTableMapping.jsx index fb909eb50a..f4a15b4302 100644 --- a/src/js/containers/aboutTheData/AgencyTableMapping.jsx +++ b/src/js/containers/aboutTheData/AgencyTableMapping.jsx @@ -8,70 +8,111 @@ import PropTypes from 'prop-types'; import { TooltipComponent, TooltipWrapper } from 'data-transparency-ui'; import { columnTooltips } from 'components/aboutTheData/componentMapping/tooltipContentMapping'; -const Tooltip = ({ title, position = 'right', className = '' }) => ( +const Tooltip = ({ + title, + id = '', + position = 'right', + className = '' +}) => ( {columnTooltips[title]} + {columnTooltips[id || title]} } /> ); Tooltip.propTypes = { title: PropTypes.string.isRequired, + id: PropTypes.string, position: PropTypes.oneOf(['left', 'right']), className: PropTypes.string }; +// these are the sub-columns that get removed for each FY +export const publicationsSubColumnPeriodFilters = { + 2020: ['P01 - P02', 'P04', 'P05'], + 2019: ['P01 - P02', 'P04', 'P05', 'P07', 'P08', 'P10', 'P11'], + 2018: ['P01 - P02', 'P04', 'P05', 'P07', 'P08', 'P10', 'P11'], + 2017: ['P01 - P02', 'P03', 'P04', 'P05', 'P07', 'P08', 'P10', 'P11'] +}; + +const publicationsSubColumnFilterFunction = (fy) => (column) => { + if (column?.subColumnNames) { + const filteredPeriodColumns = column + .subColumnNames + .filter((subColumn) => !publicationsSubColumnPeriodFilters[fy].find((period) => (period === subColumn.displayName))); + return { + ...column, + columnSpan: `${filteredPeriodColumns.length}`, + subColumnNames: filteredPeriodColumns + }; + } + return column; +}; + +export const parsePeriods = (periods) => periods.map(({ publicationDate }) => ( +
      + {(publicationDate) && publicationDate} + {!publicationDate && "--"} +
      +)); export const agenciesTableColumns = { - publications: (fy) => [ - { title: 'agency_name', displayName: 'Agency Name' }, - { - title: 'current_total_budget_authority_amount', - right: true, - displayName: 'Percent of Total Federal Budget' - }, - { - title: 'Q1', - displayName: `FY ${fy} Q1`, - columnSpan: '2', - subColumnNames: [ - { displayName: 'P01 - P02', title: 'publication_date,2' }, - { displayName: 'P03', title: 'publication_date,3' } - ] - }, - { - title: 'Q2', - displayName: `FY ${fy} Q2`, - columnSpan: '3', - subColumnNames: [ - { displayName: 'P04', title: 'publication_date,4' }, - { displayName: 'P05', title: 'publication_date,5' }, - { displayName: 'P06', title: 'publication_date,6' } - ] - }, - { - title: 'Q3', - displayName: `FY ${fy} Q3`, - columnSpan: '3', - subColumnNames: [ - { displayName: 'P07', title: 'publication_date,7' }, - { displayName: 'P08', title: 'publication_date,8' }, - { displayName: 'P09', title: 'publication_date,9' } - ] - }, - { - title: 'Q4', - displayName: `FY ${fy} Q4`, - columnSpan: '3', - subColumnNames: [ - { displayName: 'P10', title: 'publication_date,10' }, - { displayName: 'P11', title: 'publication_date,11' }, - { displayName: 'P12', title: 'publication_date,12' } - ] - } - ], + publications: (fy) => { + const columns = [ + { title: 'agency_name', displayName: 'Agency Name' }, + { + title: 'current_total_budget_authority_amount', + right: true, + displayName: 'Percent of Total Federal Budget', + icon: + }, + { + title: 'Q1', + displayName: `FY ${fy} Q1`, + columnSpan: '2', + subColumnNames: [ + { displayName: 'P01 - P02', title: 'publication_date,2' }, + { displayName: 'P03', title: 'publication_date,3' } + ] + }, + { + title: 'Q2', + displayName: `FY ${fy} Q2`, + columnSpan: '3', + subColumnNames: [ + { displayName: 'P04', title: 'publication_date,4' }, + { displayName: 'P05', title: 'publication_date,5' }, + { displayName: 'P06', title: 'publication_date,6' } + ] + }, + { + title: 'Q3', + displayName: `FY ${fy} Q3`, + columnSpan: '3', + subColumnNames: [ + { displayName: 'P07', title: 'publication_date,7' }, + { displayName: 'P08', title: 'publication_date,8' }, + { displayName: 'P09', title: 'publication_date,9' } + ] + }, + { + title: 'Q4', + displayName: `FY ${fy} Q4`, + columnSpan: '3', + subColumnNames: [ + { displayName: 'P10', title: 'publication_date,10' }, + { displayName: 'P11', title: 'publication_date,11' }, + { displayName: 'P12', title: 'publication_date,12' } + ] + } + ]; + if (!fy || parseInt(fy, 10) >= 2021) return columns; + // 2017 removes all Q1 periods, so we remove the quarter + if (fy === '2017') columns.splice(2, 1); + return columns.map(publicationsSubColumnFilterFunction(fy)); + }, submissions: [ { title: 'agency_name', @@ -80,7 +121,8 @@ export const agenciesTableColumns = { { title: 'current_total_budget_authority_amount', displayName: 'Percent of Total Federal Budget', - right: true + right: true, + icon: }, { title: 'recent_publication_date', diff --git a/src/js/containers/agencyV2/AgencyContainerV2.jsx b/src/js/containers/agencyV2/AgencyContainerV2.jsx index ae9affdc10..4093817ffa 100644 --- a/src/js/containers/agencyV2/AgencyContainerV2.jsx +++ b/src/js/containers/agencyV2/AgencyContainerV2.jsx @@ -8,7 +8,7 @@ import { useParams } from 'react-router-dom'; import { isCancel } from 'axios'; import { useDispatch } from 'react-redux'; -import { fetchAgencyOverview } from 'helpers/agencyV2Helper'; +import { fetchAgencyOverview } from 'apis/agencyV2APIs'; import { useQueryParams } from 'helpers/queryParams'; import BaseAgencyOverview from 'models/v2/agencyV2/BaseAgencyOverview'; import { setAgencyOverview } from 'redux/actions/agencyV2/agencyV2Actions'; diff --git a/src/js/containers/agencyV2/accountSpending/CountTabContainer.jsx b/src/js/containers/agencyV2/accountSpending/CountTabContainer.jsx index aadde12bd0..e0cfe73c4f 100644 --- a/src/js/containers/agencyV2/accountSpending/CountTabContainer.jsx +++ b/src/js/containers/agencyV2/accountSpending/CountTabContainer.jsx @@ -5,7 +5,7 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { fetchSpendingCount } from 'helpers/agencyV2Helper'; +import { fetchSpendingCount } from 'apis/agencyV2APIs'; import CountTab from 'components/agencyV2/CountTab'; const propTypes = { diff --git a/src/js/containers/agencyV2/accountSpending/TableContainer.jsx b/src/js/containers/agencyV2/accountSpending/TableContainer.jsx index 74c3187324..77e41e167e 100644 --- a/src/js/containers/agencyV2/accountSpending/TableContainer.jsx +++ b/src/js/containers/agencyV2/accountSpending/TableContainer.jsx @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; import { Table, Pagination } from 'data-transparency-ui'; import { accountColumns, accountFields } from 'dataMapping/agency/tableColumns'; -import { fetchSpendingByCategory } from 'helpers/agencyV2Helper'; +import { fetchSpendingByCategory } from 'apis/agencyV2APIs'; import BaseAccountSpendingRow from 'models/v2/agencyV2/BaseAccountSpendingRow'; const propTypes = { diff --git a/src/js/containers/agencyV2/visualizations/TotalObligationsOverTimeContainer.jsx b/src/js/containers/agencyV2/visualizations/TotalObligationsOverTimeContainer.jsx new file mode 100644 index 0000000000..c7b84178d9 --- /dev/null +++ b/src/js/containers/agencyV2/visualizations/TotalObligationsOverTimeContainer.jsx @@ -0,0 +1,82 @@ +/** + * TotalObligationsOverTimeContainer.jsx + * Created by Jonathan Hill 04/08/21 + */ + +import React, { useEffect, useState, useRef } from 'react'; +import PropTypes from 'prop-types'; +import { useSelector } from 'react-redux'; +import { throttle } from 'lodash'; +import { + LoadingMessage, + ErrorMessage, + NoResultsMessage +} from 'data-transparency-ui'; + +import TotalObligationsOverTimeVisualization from 'components/agencyV2/visualizations/totalObligationsOverTime/TotalObligationsOverTimeVisualization'; +import { addSubmissionEndDatesToBudgetaryResources } from 'helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper'; +import { useQueryParams } from 'helpers/queryParams'; +import { mockAgencyObligationByPeriod } from 'dataMapping/agencyV2/visualizations/totalObligationsOverTime'; + +const propTypes = { + agencyObligationsByPeriod: PropTypes.array, + error: PropTypes.shape({ + error: PropTypes.bool, + message: PropTypes.string + }) +}; + +const TotalObligationsOverTimeContainer = ({ + agencyObligationsByPeriod = mockAgencyObligationByPeriod, + error = { error: false, message: '' } +}) => { + const { fy } = useQueryParams(['fy']); + const [loading, setLoading] = useState(true); + const [data, setData] = useState([]); + const containerReference = useRef(null); + const submissionPeriods = useSelector((state) => state.account.submissionPeriods); + const [windowWidth, setWindowWidth] = useState(0); + const [visualizationWidth, setVisualizationWidth] = useState(0); + + useEffect(() => { + setLoading(true); + const javaScriptSubmissionPeriods = submissionPeriods.toJS(); + if (javaScriptSubmissionPeriods.length && agencyObligationsByPeriod.length) { + setData(addSubmissionEndDatesToBudgetaryResources(agencyObligationsByPeriod, javaScriptSubmissionPeriods, fy)); + setLoading(false); + } + }, [submissionPeriods, agencyObligationsByPeriod, fy]); + + const handleWindowResize = throttle(() => { + const wWidth = window.innerWidth; + if (windowWidth !== wWidth) { + setWindowWidth(wWidth); + setVisualizationWidth(containerReference.current.offsetWidth); + } + }, 50); + + useEffect(() => { + handleWindowResize(); + window.addEventListener('resize', handleWindowResize); + return () => { + window.removeEventListener('resize', handleWindowResize); + }; + }, []); + + return ( +
      + {error.error && } + {loading && } + {!error.error && !loading && !data.length && } + { + !error.error && !loading && data.length > 0 && + + } +
      + ); +}; +TotalObligationsOverTimeContainer.propTypes = propTypes; +export default TotalObligationsOverTimeContainer; diff --git a/src/js/containers/bulkDownload/dictionary/DataDictionaryContainer.jsx b/src/js/containers/dataDictionary/DataDictionaryContainer.jsx similarity index 64% rename from src/js/containers/bulkDownload/dictionary/DataDictionaryContainer.jsx rename to src/js/containers/dataDictionary/DataDictionaryContainer.jsx index 9617e2841c..4fa556960a 100644 --- a/src/js/containers/bulkDownload/dictionary/DataDictionaryContainer.jsx +++ b/src/js/containers/dataDictionary/DataDictionaryContainer.jsx @@ -4,14 +4,14 @@ */ import React from 'react'; - -import * as BulkDownloadHelper from 'helpers/bulkDownloadHelper'; -import DataDictionary from 'components/bulkDownload/dictionary/DataDictionary'; +import DataDictionary from 'components/dataDictionary/DataDictionary'; +import { fetchDataDictionary } from 'apis/dataDictionary'; export default class DataDictionaryContainer extends React.Component { constructor(props) { super(props); + this.request = null; this.state = { inFlight: true, error: false, @@ -25,25 +25,19 @@ export default class DataDictionaryContainer extends React.Component { }, searchTerm: '' }; - - this.request = null; - - this.loadContent = this.loadContent.bind(this); - this.changeSort = this.changeSort.bind(this); - this.setSearchString = this.setSearchString.bind(this); - } + }; componentDidMount() { this.loadContent(); } - setSearchString(searchTerm) { + setSearchString = (searchTerm) => { this.setState({ searchTerm }); - } + }; - loadContent() { + loadContent = () => { this.setState({ inFlight: true }); @@ -52,9 +46,7 @@ export default class DataDictionaryContainer extends React.Component { this.request.cancel(); } - // perform the API request - this.request = BulkDownloadHelper.requestDictionaryContent(); - + this.request = fetchDataDictionary(); this.request.promise .then((res) => { const content = res.data.document; @@ -67,27 +59,22 @@ export default class DataDictionaryContainer extends React.Component { }, () => this.parseRows(content.rows)); }) .catch((err) => { - console.log(err); + console.error(err); this.setState({ inFlight: false, error: true }); this.request = null; }); - } + }; parseRows(rows) { // replace nulls with 'N/A' - const parsedRows = rows.map((row) => - row.map((data) => - data || 'N/A' - ) - ); + const parsedRows = rows.map((row) => row.map((data) => data || 'N/A')); this.setState({ rows: parsedRows }, () => { - // Default sort this.defaultSort(); }); } @@ -98,16 +85,16 @@ export default class DataDictionaryContainer extends React.Component { } } - changeSort(field, direction) { + changeSort = (field, direction) => { // Get the index of the column we are sorting by const index = this.state.columns.findIndex((col) => col.raw === field); - // Sort the rows based on their value at that index - let rows = this.state.rows.sort((a, b) => a[index].localeCompare(b[index])); - - // Account for the sort direction + let rows; if (direction === 'desc') { - rows = rows.reverse(); + rows = this.state.rows.sort((a, b) => b[index].localeCompare(a[index])); + } + else { + rows = this.state.rows.sort((a, b) => a[index].localeCompare(b[index])); } // Update the state @@ -118,15 +105,13 @@ export default class DataDictionaryContainer extends React.Component { direction } }); - } - - render() { - return ( - - ); - } + }; + + render = () => ( + + ); } diff --git a/src/js/containers/glossary/GlossaryListener.jsx b/src/js/containers/glossary/GlossaryListener.jsx index bd4067bfba..414df56f3f 100644 --- a/src/js/containers/glossary/GlossaryListener.jsx +++ b/src/js/containers/glossary/GlossaryListener.jsx @@ -1,8 +1,11 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import { omit } from 'lodash'; import * as glossaryActions from 'redux/actions/glossary/glossaryActions'; +import { useQueryParams, getQueryParamString } from 'helpers/queryParams'; const GlossaryListener = ({ history, @@ -13,6 +16,9 @@ const GlossaryListener = ({ setTermFromUrl, Child }) => { + const { pathname, search } = useLocation(); + const queryParams = useQueryParams(); + useEffect(() => { if (location.hash) { const urlWithNoHash = location.hash.split("#").length > 1 @@ -21,16 +27,16 @@ const GlossaryListener = ({ history.replace(urlWithNoHash); } }, [location, history]); + useEffect(() => { - if (history.location.search.includes('glossary')) { - const termStr = history.location.search.split('glossary=')[1]; + if (search.includes('glossary')) { + const { glossary: term } = queryParams; showGlossary(); - setTermFromUrl(termStr); - const path = history.location.pathname; - const previousUrl = path[path.length - 1] === '/' - ? path.substr(0, path.length - 1) - : path; - history.replace(previousUrl); + setTermFromUrl(term); + history.replace({ + pathname, + search: getQueryParamString(omit(queryParams, ['glossary'])) + }); } }, [history, glossary.display, history.location.search, setTermFromUrl, showGlossary]); return ; diff --git a/src/js/containers/router/RouterRoutes.js b/src/js/containers/router/RouterRoutes.js index 81c81bfdd4..d154dc3586 100644 --- a/src/js/containers/router/RouterRoutes.js +++ b/src/js/containers/router/RouterRoutes.js @@ -4,8 +4,6 @@ **/ import React from 'react'; -import kGlobalConstants from 'GlobalConstants'; - const Homepage = React.lazy(() => import('components/homepage/Homepage').then((comp) => comp)); const SearchContainer = React.lazy(() => import('containers/search/SearchContainer').then((comp) => comp)); @@ -35,6 +33,7 @@ const AboutTheDataPage = React.lazy(() => import('components/aboutTheData/AboutT const AgencyDetailsPage = React.lazy(() => import('components/aboutTheData/AgencyDetailsPage').then((comp) => comp)); const ErrorPage = React.lazy(() => import('components/errorPage/ErrorPage').then((comp) => comp)); const SubmissionStatisticsDataSources = React.lazy(() => import('components/aboutTheData/DataSourcesAndMethodologiesPage').then((comp) => comp)); +const DataDictionaryPage = React.lazy(() => import('components/dataDictionary/DataDictionaryPage').then((comp) => comp)); // /* eslint-disable import/prefer-default-export */ // Please add any new routes to the scripts/pages.js routes file. @@ -176,14 +175,12 @@ export const routes = [ { path: '/submission-statistics', component: AboutTheDataPage, - exact: true, - hide: !kGlobalConstants.DEV && !kGlobalConstants.QAT && !kGlobalConstants.STAGING // Hidden in production + exact: true }, { path: '/submission-statistics/agency/:agencyCode', component: AgencyDetailsPage, - exact: true, - hide: !kGlobalConstants.DEV && !kGlobalConstants.QAT && !kGlobalConstants.STAGING // Hidden in production + exact: true }, { path: '/submission-statistics/data-sources', @@ -200,6 +197,11 @@ export const routes = [ component: Covid19Container, exact: true }, + { + path: '/data-dictionary', + component: DataDictionaryPage, + exact: true + }, { path: '*', component: ErrorPage diff --git a/src/js/dataMapping/agencyV2/visualizations/totalObligationsOverTime.js b/src/js/dataMapping/agencyV2/visualizations/totalObligationsOverTime.js new file mode 100644 index 0000000000..1df733dda4 --- /dev/null +++ b/src/js/dataMapping/agencyV2/visualizations/totalObligationsOverTime.js @@ -0,0 +1,72 @@ +/** + * totalObligationsOverTime.js + * Created by Jonathan Hill 04/08/21 + */ + +const xLabelHeight = 18; + +const xLabelSpacing = 4; + +const pathStrokeWidth = 1; + +export const xLabelHeightPlusPadding = xLabelHeight + xLabelSpacing; + +export const yOffsetForPathStrokeWidth = (pathStrokeWidth + 1) / 2; + +export const defaultPadding = { + top: 0, + bottom: 24, + right: 30, + left: 30 +}; + +export const mockAgencyObligationByPeriod = [ + { + period: 1, + obligated: 46698411999.28 + }, + { + period: 2, + obligated: 85901744451.98 + }, + { + period: 3, + obligated: 100689245470.66 + }, + { + period: 4, + obligated: 110898908395.86 + }, + { + period: 5, + obligated: 120898908395.86 + }, + { + period: 6, + obligated: 10689245470.86 + }, + { + period: 7, + obligated: 140898908395.86 + }, + { + period: 8, + obligated: 190689245470.86 + }, + { + period: 9, + obligated: 160898908395.86 + }, + { + period: 10, + obligated: 170898908395.86 + }, + { + period: 11, + obligated: 180898908395.86 + }, + { + period: 12, + obligated: 185898908395.86 + } +]; diff --git a/src/js/dataMapping/navigation/menuOptions.jsx b/src/js/dataMapping/navigation/menuOptions.jsx index 5b35cab8ca..985ecf0f60 100644 --- a/src/js/dataMapping/navigation/menuOptions.jsx +++ b/src/js/dataMapping/navigation/menuOptions.jsx @@ -65,8 +65,8 @@ export const resourceOptions = [ }, { label: 'Data Dictionary', - type: 'data_dictionary', - url: '/download_center/data_dictionary', + type: 'data-dictionary', + url: '/data-dictionary', code: 'dictionary', description: '', callToAction: 'Explore the Data Dictionary', @@ -168,8 +168,8 @@ export const downloadOptions = [ }, { label: 'Data Dictionary', - type: 'data_dictionary', - url: '/download_center/data_dictionary', + type: 'data-dictionary', + url: '/data-dictionary', code: 'dictionary', description: '', callToAction: 'Explore the Data Dictionary', diff --git a/src/js/dataMapping/search/geoVisualizationSection.jsx b/src/js/dataMapping/search/geoVisualizationSection.jsx new file mode 100644 index 0000000000..9faf2e81e0 --- /dev/null +++ b/src/js/dataMapping/search/geoVisualizationSection.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +export const noteMessage = ( + <> + Data reported by the Department of Health and Human Services (HHS) related + to Medicare payments does not reflect the place where "the majority of the work" occurs, as + required by USAspending's data model specifications. For more information, visit our  + About Page. + +); diff --git a/src/js/helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper.js b/src/js/helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper.js new file mode 100644 index 0000000000..e230bd99ce --- /dev/null +++ b/src/js/helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper.js @@ -0,0 +1,35 @@ +/** + * TotalObligationsOverTimeVisualizationHelper.js + * Created by Jonathan Hill 04/09/2021 + */ + +export const getYDomain = (data) => { + const obligatedAmounts = data.map((x) => x.obligated); + return [0, Math.max(...obligatedAmounts)]; +}; + +export const getMilliseconds = (date) => date.getTime(); + +const converISODateToDate = (date) => { + const dateString = date.split('T')[0].split('-'); + const newDate = new Date(parseInt(dateString[0], 10), parseInt(dateString[1], 10) - 1, parseInt(dateString[2], 10)); + return newDate; +}; + +export const addSubmissionEndDatesToBudgetaryResources = (budgetaryResources, submissionPeriods, fy) => { + const yearlySubmissions = submissionPeriods.filter((period) => `${period.submission_fiscal_year}` === fy); + return budgetaryResources + .map((budgetaryResource) => { + /* eslint-disable camelcase */ + const yearlySubmissionEndDateByPeriod = yearlySubmissions.find((submission) => submission.submission_fiscal_month === budgetaryResource.period)?.period_end_date; + if (yearlySubmissionEndDateByPeriod) { + return { + ...budgetaryResource, + /* eslint-disable camelcase */ + endDate: getMilliseconds(new Date(converISODateToDate(yearlySubmissionEndDateByPeriod))) + }; + } + return null; + }) + .filter((budgetaryResource) => budgetaryResource); +}; diff --git a/src/js/helpers/bulkDownloadHelper.js b/src/js/helpers/bulkDownloadHelper.js index 72a4902f1b..2dc0204e38 100644 --- a/src/js/helpers/bulkDownloadHelper.js +++ b/src/js/helpers/bulkDownloadHelper.js @@ -57,10 +57,6 @@ export const requestArchiveFiles = (params) => apiRequest({ data: params }); -export const requestDictionaryContent = () => apiRequest({ - url: 'v2/references/data_dictionary/' -}); - export const areDefCodesDisabled = (accountDownloadSubmissionTypeSelections) => ( accountDownloadSubmissionTypeSelections?.length === 1 && accountDownloadSubmissionTypeSelections?.includes('accountBalances') diff --git a/src/js/helpers/glossaryHelper.js b/src/js/helpers/glossaryHelper.js index 78f7d9a5d2..700b249708 100644 --- a/src/js/helpers/glossaryHelper.js +++ b/src/js/helpers/glossaryHelper.js @@ -16,16 +16,22 @@ export const fetchSearchResults = (params) => apiRequest({ data: params }); -export const getNewUrlForGlossary = (pathname, url, queryParams = '') => { - if (pathname === '/' && !queryParams) return url; - if (pathname[pathname.length - 1] === '/' && !queryParams) { - return `${pathname.substr(0, pathname.length - 1)}${url}`; +export const getNewUrlForGlossary = (existingUrl, glossaryFragment, existingQueryParams = '') => { + if (existingUrl === '/' && !existingQueryParams) return glossaryFragment; + if (existingUrl[existingUrl.length - 1] === '/' && !existingQueryParams) { + return `${existingUrl.substr(0, existingUrl.length - 1)}${glossaryFragment}`; } - if (queryParams && pathname[pathname.length - 1] === '/') { - const cleanQueryParams = queryParams[0] === '?' - ? queryParams.substr(1) - : queryParams; - return `${pathname.substr(0, pathname.length - 1)}${url}&${cleanQueryParams}`; + if (existingQueryParams && existingUrl[existingUrl.length - 1] === '/') { + const cleanQueryParams = existingQueryParams[0] === '?' + ? existingQueryParams.substr(1) + : existingQueryParams; + return `${existingUrl.substr(0, existingUrl.length - 1)}${glossaryFragment}&${cleanQueryParams}`; } - return `${pathname}${url}`; + if (existingQueryParams) { + const cleanQueryParams = existingQueryParams[0] === '?' + ? existingQueryParams.substr(1) + : existingQueryParams; + return `${existingUrl}${glossaryFragment}&${cleanQueryParams}`; + } + return `${existingUrl}${glossaryFragment}`; }; diff --git a/src/js/helpers/metaTagHelper.js b/src/js/helpers/metaTagHelper.js index 5b89b765bc..563cf25023 100644 --- a/src/js/helpers/metaTagHelper.js +++ b/src/js/helpers/metaTagHelper.js @@ -179,7 +179,7 @@ export const downloadAccountPageMetaTags = { }; export const dataDictionaryPageMetaTags = { - og_url: `${productionURL}download_center/data_dictionary`, + og_url: `${productionURL}data-dictionary`, og_title: 'Data Dictionary | USAspending', og_description: 'Learn about the data elements in our download packages by visiting this page.', @@ -265,6 +265,27 @@ export const stateLandingPageMetaTags = { og_image: `${productionURL}${imgDirectory}${facebookImage}` }; +export const aboutTheDataMetaTags = { + og_url: `${productionURL}submission-statistics`, + og_title: 'Agency Submission Statistics | USAspending', + og_description: + 'Federal agencies are required to regularly submit financial data to USAspending.gov. See the latest status and content of these submissions.', + og_site_name: siteName, + og_image: `${productionURL}${imgDirectory}${facebookImage}` +}; + +export const aboutTheDataAgencyDetails = ({ + id, + name +}) => ({ + og_url: `${productionURL}submission-statistics/agency/${id}`, + og_title: `${name} | Submission Statistics | USAspending`, + og_description: + `See the latest financial data from ${name}, submitted to USAspending.gov in compliance with the 2014 Data Act.`, + og_site_name: siteName, + og_image: `${productionURL}${imgDirectory}${facebookImage}` +}); + /* eslint-enable max-len */ export const isCustomPageTitleDefined = (title = "USAspending.gov") => { diff --git a/src/js/models/v2/aboutTheData/BaseReportingPeriodRow.js b/src/js/models/v2/aboutTheData/BaseReportingPeriodRow.js index 8c84b699c9..9a5f84889f 100644 --- a/src/js/models/v2/aboutTheData/BaseReportingPeriodRow.js +++ b/src/js/models/v2/aboutTheData/BaseReportingPeriodRow.js @@ -2,7 +2,7 @@ * BaseReportingPeriodRow.js * Created by Lizzie Salita 12/8/20 */ - +import { isNull } from 'lodash'; import { formatNumberWithPrecision } from 'helpers/moneyFormatter'; import { getPeriodWithTitleById } from 'helpers/aboutTheDataHelper'; import CoreReportingRow from './CoreReportingRow'; @@ -14,8 +14,8 @@ BaseReportingPeriodRow.populate = function populate(data) { this.fiscalYear = parseInt(data.fiscal_year, 10) || 0; this.fiscalPeriod = parseInt(data.fiscal_period, 10) || 0; this.reportingPeriod = `FY ${this.fiscalYear}: ${getPeriodWithTitleById(`${this.fiscalPeriod}`).title}`; - this._percentOfBudget = data.percent_of_total_budgetary_resources || 0; - this.percentOfBudget = `${formatNumberWithPrecision(this._percentOfBudget, 2)}%`; + this._percentOfBudget = data.percent_of_total_budgetary_resources; + this.percentOfBudget = isNull(this._percentOfBudget) ? '--' : `${formatNumberWithPrecision(this._percentOfBudget, 2)}%`; }; export default BaseReportingPeriodRow; diff --git a/src/js/models/v2/aboutTheData/CoreReportingRow.js b/src/js/models/v2/aboutTheData/CoreReportingRow.js index 1c3a97288c..5064565c21 100644 --- a/src/js/models/v2/aboutTheData/CoreReportingRow.js +++ b/src/js/models/v2/aboutTheData/CoreReportingRow.js @@ -4,6 +4,7 @@ */ import { formatNumber, formatMoneyWithPrecision } from 'helpers/moneyFormatter'; +import { isNull } from 'lodash'; import { format } from 'date-fns'; const CoreReportingRow = { @@ -12,11 +13,11 @@ const CoreReportingRow = { this._mostRecentPublicationDate = data.recent_publication_date || null; /* eslint-disable camelcase */ this._gtasObligationTotal = data.tas_account_discrepancies_totals?.gtas_obligation_total; - this._discrepancyCount = data.tas_account_discrepancies_totals?.missing_tas_accounts_count || 0; + this._discrepancyCount = data.tas_account_discrepancies_totals?.missing_tas_accounts_count; /* eslint-enable camelcase */ this._obligationDifference = data.obligation_difference; - this._unlinkedContracts = data.unlinked_contract_award_count || 0; - this._unlinkedAssistance = data.unlinked_assistance_award_count || 0; + this._unlinkedContracts = data.unlinked_contract_award_count; + this._unlinkedAssistance = data.unlinked_assistance_award_count; this.assuranceStatement = data.assurance_statement_url || ''; }, get budgetAuthority() { @@ -29,13 +30,13 @@ const CoreReportingRow = { return formatMoneyWithPrecision(this._obligationDifference, 2, '--'); }, get discrepancyCount() { - return formatNumber(this._discrepancyCount); + return isNull(this._discrepancyCount) ? '--' : formatNumber(this._discrepancyCount); }, get unlinkedContracts() { - return formatNumber(this._unlinkedContracts); + return isNull(this._unlinkedContracts) ? '--' : formatNumber(this._unlinkedContracts); }, get unlinkedAssistance() { - return formatNumber(this._unlinkedAssistance); + return isNull(this._unlinkedAssistance) ? '--' : formatNumber(this._unlinkedAssistance); } }; diff --git a/src/js/models/v2/aboutTheData/PublicationOverviewRow.js b/src/js/models/v2/aboutTheData/PublicationOverviewRow.js index 61fca6ad18..cc83ad6260 100644 --- a/src/js/models/v2/aboutTheData/PublicationOverviewRow.js +++ b/src/js/models/v2/aboutTheData/PublicationOverviewRow.js @@ -5,38 +5,25 @@ import { formatMoney, formatNumber, calculatePercentage } from 'helpers/moneyFormatter'; import moment from 'moment'; -const addFuturePeriods = (periods) => { - if (periods.length === 12) return periods; - return periods - .concat( - new Array(11 - periods.length) - .fill() - .map(() => ({ - quarterly: false, - submission_dates: { certification_date: '--', publication_date: '--' } - })) - ); -}; - -const DatesRow = { +const PublicationOverviewRow = { populate(data, federalTotal) { this._name = data.agency_name || ''; this._abbreviation = data.abbreviation || ''; this.code = data.toptier_code || ''; - this._budgetAuthority = data.current_total_budget_authority_amount || 0; + this._budgetAuthority = data.current_total_budget_authority_amount; this._federalTotal = federalTotal; - this.periods = addFuturePeriods(data.periods) - .map(({ submission_dates: { publication_date: p, certification_date: c }, quarterly: isQuarterly }) => { + this.periods = data.periods + .map(({ submission_dates: { publication_date: p, certification_date: c }, quarterly: isQuarterly, period }) => { if (p === '--') { return { - isQuarterly, publicationDate: p, certificationDate: c, showNotCertified: false + isQuarterly, publicationDate: p, certificationDate: c, period }; }; return { publicationDate: p ? moment(p).format('MM/DD/YYYY') : null, certificationDate: c ? moment(c).format('MM/DD/YYYY') : null, - showNotCertified: c ? moment(c).isAfter(moment()) : false, - isQuarterly + isQuarterly, + period }; }); }, @@ -64,4 +51,4 @@ const DatesRow = { } }; -export default DatesRow; +export default PublicationOverviewRow; diff --git a/tests/components/agency/AccountSpending-test.jsx b/tests/components/agency/AccountSpending-test.jsx index 17bd12a796..e1bbc84631 100644 --- a/tests/components/agency/AccountSpending-test.jsx +++ b/tests/components/agency/AccountSpending-test.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { render, waitFor } from 'test-utils'; -import * as helpers from 'helpers/agencyV2Helper'; +import * as agencyV2APIs from 'apis/agencyV2APIs'; import AccountSpending from 'components/agencyV2/accountSpending/AccountSpending'; const defaultProps = { @@ -17,29 +17,23 @@ let fetchSpendingCountSpy; let fetchSpendingByCategorySpy; beforeEach(() => { - fetchBudgetaryResourcesSpy = jest.spyOn(helpers, 'fetchBudgetaryResources').mockReturnValue({ - promise: new Promise((resolve) => { - return process.nextTick(() => (resolve(mockData))); - }), + fetchBudgetaryResourcesSpy = jest.spyOn(agencyV2APIs, 'fetchBudgetaryResources').mockReturnValue({ + promise: new Promise((resolve) => process.nextTick(() => (resolve(mockData)))), cancel: jest.fn() }).mockClear(); - fetchSpendingCountSpy = jest.spyOn(helpers, 'fetchSpendingCount').mockReturnValue({ - promise: new Promise((resolve) => { - return process.nextTick(() => (resolve(mockData))); - }), + fetchSpendingCountSpy = jest.spyOn(agencyV2APIs, 'fetchSpendingCount').mockReturnValue({ + promise: new Promise((resolve) => process.nextTick(() => (resolve(mockData)))), cancel: jest.fn() }).mockClear(); - fetchSpendingByCategorySpy = jest.spyOn(helpers, 'fetchSpendingByCategory').mockReturnValue({ - promise: new Promise((resolve) => { - return process.nextTick(() => (resolve({ - data: { - page_metadata: { - total: 1 - }, - results: [] - } - }))); - }), + fetchSpendingByCategorySpy = jest.spyOn(agencyV2APIs, 'fetchSpendingByCategory').mockReturnValue({ + promise: new Promise((resolve) => process.nextTick(() => (resolve({ + data: { + page_metadata: { + total: 1 + }, + results: [] + } + })))), cancel: jest.fn() }).mockClear(); }); diff --git a/tests/components/agency/AgencyOverview-test.jsx b/tests/components/agency/AgencyOverview-test.jsx index 429ee11763..8f6586fc6a 100644 --- a/tests/components/agency/AgencyOverview-test.jsx +++ b/tests/components/agency/AgencyOverview-test.jsx @@ -5,7 +5,7 @@ import React from 'react'; import { render, screen } from 'test-utils'; -import AgencyOverview from 'components/agencyV2/AgencyOverview'; +import AgencyOverview from 'components/agencyV2/overview/AgencyOverview'; import BaseAgencyOverview from 'models/v2/agencyV2/BaseAgencyOverview'; import { mockAgency } from '../../models/agency/BaseAgencyOverview-test'; diff --git a/tests/containers/aboutTheData/mockData.js b/tests/containers/aboutTheData/mockData.js index 6adb27733a..6935ec9937 100644 --- a/tests/containers/aboutTheData/mockData.js +++ b/tests/containers/aboutTheData/mockData.js @@ -165,11 +165,12 @@ export const mockReportingPeriodRow = { tas_obligation_not_in_gtas_total: 343345, missing_tas_accounts_count: 20 }, - percent_of_total_budgetary_resources: 2.189, + percent_of_total_budgetary_resources: 2.1, obligation_difference: 4000.00, unlinked_contract_award_count: 20002, unlinked_assistance_award_count: 10001, - assurance_statement_url: 'https://files.usaspending.gov/agency_submissions/Raw%20DATA%20Act%20Files/2020/Q1/MockAgency(ABC)-Assurance_Statement.txt'} + assurance_statement_url: 'https://files.usaspending.gov/agency_submissions/Raw%20DATA%20Act%20Files/2020/Q1/MockAgency(ABC)-Assurance_Statement.txt' + } ] } }; diff --git a/tests/containers/agencyV2/AgencyContainerV2-test.jsx b/tests/containers/agencyV2/AgencyContainerV2-test.jsx index cc04e42f54..a4586d1572 100644 --- a/tests/containers/agencyV2/AgencyContainerV2-test.jsx +++ b/tests/containers/agencyV2/AgencyContainerV2-test.jsx @@ -6,7 +6,7 @@ import React from 'react'; import { render, screen, waitFor } from 'test-utils'; import { Route } from 'react-router-dom'; -import * as agencyV2Helper from 'helpers/agencyV2Helper'; +import * as agencyV2APIs from 'apis/agencyV2APIs'; import * as accountHooks from 'containers/account/WithLatestFy'; import * as queryParamHelpers from 'helpers/queryParams'; @@ -24,24 +24,18 @@ const mockResponse = { let spy; beforeEach(() => { - jest.spyOn(accountHooks, "useLatestAccountData").mockImplementation(() => { - return [ + jest.spyOn(accountHooks, "useLatestAccountData").mockImplementation(() => [ null, [], { year: 2020 } - ]; - }); - jest.spyOn(accountHooks, "useValidTimeBasedQueryParams").mockImplementation(() => { - return [ + ]); + jest.spyOn(accountHooks, "useValidTimeBasedQueryParams").mockImplementation(() => [ 2020, () => { } - ]; - }); - jest.spyOn(queryParamHelpers, "useQueryParams").mockImplementation(() => { - return [ + ]); + jest.spyOn(queryParamHelpers, "useQueryParams").mockImplementation(() => [ { fy: 2020 } - ]; - }); + ]); }); test('on network error, an error message displays', () => { @@ -52,7 +46,7 @@ test('on network error, an error message displays', () => { )); }) }; - spy = jest.spyOn(agencyV2Helper, 'fetchAgencyOverview').mockReturnValueOnce(mockReject); + spy = jest.spyOn(agencyV2APIs, 'fetchAgencyOverview').mockReturnValueOnce(mockReject); render(( @@ -65,7 +59,7 @@ test('on network error, an error message displays', () => { test('an API request is made for the agency code in the URL', () => { spy.mockClear(); - spy = jest.spyOn(agencyV2Helper, 'fetchAgencyOverview').mockReturnValueOnce(mockResponse); + spy = jest.spyOn(agencyV2APIs, 'fetchAgencyOverview').mockReturnValueOnce(mockResponse); render(( diff --git a/tests/containers/bulkDownload/dictionary/DataDictionaryContainer-test.jsx b/tests/containers/bulkDownload/dictionary/DataDictionaryContainer-test.jsx deleted file mode 100644 index 05d2f8b6fc..0000000000 --- a/tests/containers/bulkDownload/dictionary/DataDictionaryContainer-test.jsx +++ /dev/null @@ -1,66 +0,0 @@ -/** - * DataDictionaryContainer-test.jsx - * Created by Lizzie Salita 10/1/2018 - */ - -import React from 'react'; -import { mount } from 'enzyme'; -import DataDictionaryContainer from 'containers/bulkDownload/dictionary/DataDictionaryContainer'; -import { mockDictionary } from '../mockData'; - -// mock the bulkDownload helper -jest.mock('helpers/bulkDownloadHelper', () => require('../mockBulkDownloadHelper')); - -// mock the child component by replacing it with a function that returns a null element -jest.mock('components/bulkDownload/dictionary/DataDictionary', () => jest.fn(() => null)); - -describe('DataDictionaryContainer', () => { - it('should make an API call for the dictionary content on mount and save res data to the state', async () => { - const container = mount(); - - await container.instance().request.promise; - - expect(container.state().sections).toEqual(mockDictionary.document.sections); - expect(container.state().columns).toEqual(mockDictionary.document.headers); - expect(container.state().downloadLocation).toEqual(mockDictionary.document.metadata.download_location); - }); - describe('parseRows', () => { - it('should replace null values with N/A and update the state', () => { - const container = mount(); - - const mockRows = [ - ['A', 'B', 'C', null] - ]; - - const expectedRows = [ - ['A', 'B', 'C', 'N/A'] - ]; - - container.instance().parseRows(mockRows); - - expect(container.state().rows).toEqual(expectedRows); - }); - }); - describe('changeSort', () => { - it('should change the order of rows and update the state', async () => { - const container = mount(); - - await container.instance().request.promise; - - container.instance().changeSort('file', 'asc'); - - const expectedRows = [ - ['B', 'dolor sit amet', '1890', 'X'], - ['C', 'consectetur adipiscing elit', '1994', 'Y'], - ['A', 'Lorem ipsum', '1862', 'Z'] - ]; - - expect(container.state().sort).toEqual({ - field: 'file', - direction: 'asc' - }); - - expect(container.state().rows).toEqual(expectedRows); - }); - }); -}); diff --git a/tests/containers/bulkDownload/mockBulkDownloadHelper.js b/tests/containers/bulkDownload/mockBulkDownloadHelper.js index 0d393804b0..13eb1a62f3 100644 --- a/tests/containers/bulkDownload/mockBulkDownloadHelper.js +++ b/tests/containers/bulkDownload/mockBulkDownloadHelper.js @@ -1,6 +1,6 @@ import { mockStatusResponse, mockBudgetFunctions, mockBudgetSubfunctions, mockAwardDownloadResponse, mockAgencies, mockArchiveResponse, - mockFederalAccounts, mockDictionary } from './mockData'; + mockFederalAccounts } from './mockData'; export const requestAgenciesList = () => ({ promise: new Promise((resolve) => { @@ -87,14 +87,3 @@ export const requestArchiveFiles = () => ({ }), cancel: jest.fn() }); - -export const requestDictionaryContent = () => ({ - promise: new Promise((resolve) => { - process.nextTick(() => { - resolve({ - data: mockDictionary - }); - }); - }), - cancel: jest.fn() -}); diff --git a/tests/containers/bulkDownload/mockData.js b/tests/containers/bulkDownload/mockData.js index 4b939d4d04..6c9b75cf72 100644 --- a/tests/containers/bulkDownload/mockData.js +++ b/tests/containers/bulkDownload/mockData.js @@ -201,62 +201,3 @@ export const mockArchiveResponse = { export const mockParams = { type: '' }; - -export const mockDictionary = { - document: { - metadata: { - total_rows: 3, - total_columns: 4, - total_size: '100.00KB', - download_location: 'http://files-nonprod.usaspending.gov/docs/DATA+Transparency+Crosswalk.xlsx' - }, - sections: [ - { - section: 'Section One', - colspan: 3 - }, - { - section: 'Section Two', - colspan: 1 - } - ], - headers: [ - { - display: 'Element', - raw: 'element' - }, - { - display: 'Definition', - raw: 'definition' - }, - { - display: 'Name', - raw: 'name' - }, - { - display: 'File', - raw: 'file' - } - ], - rows: [ - [ - 'A', - 'Lorem ipsum', - '1862', - 'Z' - ], - [ - 'B', - 'dolor sit amet', - '1890', - 'X' - ], - [ - 'C', - 'consectetur adipiscing elit', - '1994', - 'Y' - ] - ] - } -}; diff --git a/tests/containers/dataDictionary/DataDictionaryContainer-test.jsx b/tests/containers/dataDictionary/DataDictionaryContainer-test.jsx new file mode 100644 index 0000000000..2c388ca343 --- /dev/null +++ b/tests/containers/dataDictionary/DataDictionaryContainer-test.jsx @@ -0,0 +1,66 @@ +/** + * DataDictionaryContainer-test.jsx + * Created by Lizzie Salita 10/1/2018 + */ + + import React from 'react'; + import { mount } from 'enzyme'; + import DataDictionaryContainer from 'containers/dataDictionary/DataDictionaryContainer'; + + import { mockApiCall } from '../../testResources/mockApiHelper'; + import * as api from 'helpers/apiRequest'; + import { mockDictionary } from '../../mockApi/responses/dataDictionary.js'; + + mockApiCall(api, 'apiRequest', mockDictionary); + + // mock the child component by replacing it with a function that returns a null element + jest.mock('components/dataDictionary/DataDictionary', () => jest.fn(() => null)); + + describe('DataDictionaryContainer', () => { + it('should make an API call for the dictionary content on mount and save res data to the state', async () => { + const container = mount(); + + await container.instance().request.promise; + + console.log(container.state()); + + expect(container.state().sections).toEqual(mockDictionary.data.document.sections); + expect(container.state().columns).toEqual(mockDictionary.data.document.headers); + expect(container.state().downloadLocation).toEqual(mockDictionary.data.document.metadata.download_location); + }); + describe('parseRows', () => { + it('should replace null values with N/A and update the state', () => { + const container = mount(); + + const mockRows = [['A', 'B', 'C', null]]; + const expectedRows = [['A', 'B', 'C', 'N/A']]; + + container.instance().parseRows(mockRows); + expect(container.state().rows).toEqual(expectedRows); + }); + }); + describe('changeSort', () => { + it('should change the order of rows and update the state', async () => { + const container = mount(); + + await container.instance().request.promise; + + console.log(container.state()); + + container.instance().changeSort('file', 'asc'); + + const expectedRows = [ + ['B', 'dolor sit amet', '1890', 'X'], + ['C', 'consectetur adipiscing elit', '1994', 'Y'], + ['A', 'Lorem ipsum', '1862', 'Z'] + ]; + + expect(container.state().sort).toEqual({ + field: 'file', + direction: 'asc' + }); + + expect(container.state().rows).toEqual(expectedRows); + }); + }); + }); diff --git a/tests/helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper-test.js b/tests/helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper-test.js new file mode 100644 index 0000000000..ab05118b0b --- /dev/null +++ b/tests/helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper-test.js @@ -0,0 +1,33 @@ +import { addSubmissionEndDatesToBudgetaryResources } from 'helpers/agencyV2/visualizations/TotalObligationsOverTimeVisualizationHelper'; +import { mockSubmissions } from '../../../mockData/helpers/aboutTheDataHelper'; + +const mockBudgetaryResources = [ + { + period: 3, + obligated: 100689245470.66 + }, + { + period: 4, + obligated: 110898908395.86 + }, + { + period: 5, + obligated: 120898908395.86 + }, + { + period: 6, + obligated: 10689245470.86 + } +]; + +describe('Total Obligations Over Time Visualization Helper', () => { + it('should return an object with endDate, period, and obligated properties', () => { + const data = addSubmissionEndDatesToBudgetaryResources(mockBudgetaryResources, mockSubmissions, '2020'); + expect(data[0]).toHaveProperty('period', 3); + expect(typeof data[0].obligated === 'number').toBeTruthy(); + expect(typeof data[0].endDate === 'number').toBeTruthy(); + expect(data[1]).toHaveProperty('period', 6); + expect(typeof data[1].obligated === 'number').toBeTruthy(); + expect(typeof data[1].endDate === 'number').toBeTruthy(); + }); +}); diff --git a/tests/helpers/glossaryHelper-test.js b/tests/helpers/glossaryHelper-test.js index 691e43a0fb..f49759f486 100644 --- a/tests/helpers/glossaryHelper-test.js +++ b/tests/helpers/glossaryHelper-test.js @@ -1,10 +1,14 @@ import { getNewUrlForGlossary } from 'helpers/glossaryHelper'; +const glossaryFragment = '?glossary='; + test.each([ // name, fn - ['/', '/?glossary=', ''], - ['/search', '/search/?glossary=', ''], - ['/submission-statistics/', '/submission-statistics/?glossary=&fy=2020&period=12', '?fy=2020&period=12'] -])('when existing url is %s, the glossary query param is appended correctly as %s', (existingUrl, expected, search = '') => { - expect(getNewUrlForGlossary(existingUrl, '/?glossary=', search)).toEqual(expected); + ['/', glossaryFragment, ''], + ['/search', `/search${glossaryFragment}`, ''], + ['/submission-statistics/', `/submission-statistics${glossaryFragment}&fy=2020&period=12`, '?fy=2020&period=12'], + ['/test', `/test${glossaryFragment}&test=123`, '?test=123'], + ['/test', `/test${glossaryFragment}&a=123&b=456`, '?a=123&b=456'] +])('when existing url is %s, the glossary query param is appended correctly as %s', (existingUrl, expected, existingParams = '') => { + expect(getNewUrlForGlossary(existingUrl, glossaryFragment, existingParams)).toEqual(expected); }); diff --git a/tests/mockApi/responses/dataDictionary.js b/tests/mockApi/responses/dataDictionary.js new file mode 100644 index 0000000000..a5446aab92 --- /dev/null +++ b/tests/mockApi/responses/dataDictionary.js @@ -0,0 +1,60 @@ +export const mockDictionary = { + data: { + document: { + metadata: { + total_rows: 3, + total_columns: 4, + total_size: '100.00KB', + download_location: 'http://files-nonprod.usaspending.gov/docs/DATA+Transparency+Crosswalk.xlsx' + }, + sections: [ + { + section: 'Section One', + colspan: 3 + }, + { + section: 'Section Two', + colspan: 1 + } + ], + headers: [ + { + display: 'Element', + raw: 'element' + }, + { + display: 'Definition', + raw: 'definition' + }, + { + display: 'Name', + raw: 'name' + }, + { + display: 'File', + raw: 'file' + } + ], + rows: [ + [ + 'A', + 'Lorem ipsum', + '1862', + 'Z' + ], + [ + 'B', + 'dolor sit amet', + '1890', + 'X' + ], + [ + 'C', + 'consectetur adipiscing elit', + '1994', + 'Y' + ] + ] + } + } +}; diff --git a/tests/models/aboutTheData/BaseReportingPeriodRow-test.js b/tests/models/aboutTheData/BaseReportingPeriodRow-test.js index b45989f9a5..8298b4936e 100644 --- a/tests/models/aboutTheData/BaseReportingPeriodRow-test.js +++ b/tests/models/aboutTheData/BaseReportingPeriodRow-test.js @@ -34,10 +34,22 @@ describe('BaseReportingPeriodRow', () => { reportingPeriodRowMod.populate(period2); expect(reportingPeriodRowMod.reportingPeriod).toEqual('FY 2020: P01 - P02'); }); - it('should format the percent of budgetary resources', () => { - expect(reportingPeriodRow.percentOfBudget).toEqual('2.19%'); - }); it('should have CoreReportingRow in its prototype chain', () => { expect(Object.getPrototypeOf(reportingPeriodRow)).toMatchObject(CoreReportingRow); }); + it('should format the percent of budgetary resources', () => { + expect(reportingPeriodRow.percentOfBudget).toEqual('2.10%'); + }); + it('should handle null percent of budgetary resources', () => { + const noPercentage = mockReportingPeriodRow.data.results[0]; + noPercentage.percent_of_total_budgetary_resources = null; + reportingPeriodRow.populate(noPercentage); + expect(reportingPeriodRow.percentOfBudget).toEqual('--'); + }); + it('should handle 0 percent of budgetary resources', () => { + const noPercentage = mockReportingPeriodRow.data.results[0]; + noPercentage.percent_of_total_budgetary_resources = 0; + reportingPeriodRow.populate(noPercentage); + expect(reportingPeriodRow.percentOfBudget).toEqual('0.00%'); + }); }); diff --git a/tests/models/aboutTheData/CoreReportingRow-test.js b/tests/models/aboutTheData/CoreReportingRow-test.js index 1c1f5df95d..d6ef346db6 100644 --- a/tests/models/aboutTheData/CoreReportingRow-test.js +++ b/tests/models/aboutTheData/CoreReportingRow-test.js @@ -19,10 +19,17 @@ test.each([ ['obligationDifference', '--', { ...mockReportingPeriodRow, obligation_difference: null }], ['obligationDifference', '$0.00', { ...mockReportingPeriodRow, obligation_difference: 0 }], ['obligationDifference', '$4,000.00'], + ['_discrepancyCount', null, { ...mockReportingPeriodRow, tas_account_discrepancies_totals: { ...mockReportingPeriodRow.tas_account_discrepancies_totals, missing_tas_accounts_count: null } }], + ['discrepancyCount', '--', { ...mockReportingPeriodRow, tas_account_discrepancies_totals: { ...mockReportingPeriodRow.tas_account_discrepancies_totals, missing_tas_accounts_count: null } }], ['discrepancyCount', '2,000'], ['mostRecentPublicationDate', '01/10/2020'], + ['_gtasObligationTotal', null, { ...mockReportingPeriodRow, tas_account_discrepancies_totals: { ...mockReportingPeriodRow.tas_account_discrepancies_totals, gtas_obligation_total: null } }], ['_gtasObligationTotal', 50000], + ['_unlinkedContracts', null, { ...mockReportingPeriodRow, unlinked_contract_award_count: null }], + ['unlinkedContracts', '--', { ...mockReportingPeriodRow, unlinked_contract_award_count: null }], ['unlinkedContracts', '20,002'], + ['_unlinkedAssistance', null, { ...mockReportingPeriodRow, unlinked_assistance_award_count: null }], + ['unlinkedAssistance', '--', { ...mockReportingPeriodRow, unlinked_assistance_award_count: null }], ['unlinkedAssistance', '10,001'] ])('should format the property %s as %s', (property, output, sourceData = mockReportingPeriodRow) => { const row = Object.create(CoreReportingRow); diff --git a/tests/models/aboutTheData/PublicationOverviewRow-test.js b/tests/models/aboutTheData/PublicationOverviewRow-test.js index 7d1f6f5640..db909a78cc 100644 --- a/tests/models/aboutTheData/PublicationOverviewRow-test.js +++ b/tests/models/aboutTheData/PublicationOverviewRow-test.js @@ -22,16 +22,20 @@ test('should handle an agency with no abbreviation', () => { abbreviation: '' }; const mockDatesRowMod = Object.create(mockDatesRow); - mockDatesRowMod.populate(missingAbbrev, mockTotal); + mockDatesRowMod.populate(missingAbbrev, mockTotal, 2021); expect(mockDatesRowMod.name).toEqual('Mock Agency'); }); -test('should format the percent of total federal budget', () => { - // 8000.72 / 10000 - expect(mockDatesRow.percentageOfTotalFederalBudget).toEqual('80.01%'); -}); - -test('should always have 11 periods', () => { - expect(mockDatesRow.periods.length).toEqual(11); +test.each([ + [mockTotal, 8000.72, '80.01%'], + [mockTotal, null, '--'], + [null, 100, '--'] +])('when overall total is %s and agency total is %s, percent of total budget is %s ', (overallTotal, agencyTotal, expected) => { + const model = Object.create(PublicationOverviewRow); + const results = { + ...mockRow, + current_total_budget_authority_amount: agencyTotal + }; + model.populate(results, overallTotal); + expect(model.percentageOfTotalFederalBudget).toEqual(expected); }); - diff --git a/tests/testResources/mockApiHelper.js b/tests/testResources/mockApiHelper.js new file mode 100644 index 0000000000..7323bb8892 --- /dev/null +++ b/tests/testResources/mockApiHelper.js @@ -0,0 +1,16 @@ +/** + * mockApiHelper.js + * Created by Max Kendall 04/23/2021 +*/ + +/** + * @param {object} mod the module from which which the method is exported; ex, import * as mod from 'some/path/' + * @param {method} string the method on the module which is being mocked + * @param {response} object which mocks the response +*/ +export const mockApiCall = (mod, method, response) => { + return jest.spyOn(mod, method).mockReturnValue({ + promise: Promise.resolve(response), + cancel: () => { } + }); +};