diff --git a/CHANGELOG.md b/CHANGELOG.md index 847a88e80..82784f37e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [3.3.2] - 2023-11-09: workspace search fails on frontera portal +- WP-380: Remove id prefix filter on workspace search (#898) + +## [3.3.1] - 2023-11-07: Search related bug fixes and App Icon fix +- WP-355: Fixing issue with icons on dev/prod sites (#892) +- WP-354: Workspace search - filter results visible to user (#893) +- WP-356: Site Search: For files, use home dir to isolate the search (#897) +- WP-361: Jobs Search - restrict search to a specific portal (#896) + +## [3.3.0] - 2023-10-27: V3 integration improvements; bug fixes + +### Added + +- WP-164 Implement Workspace Search (#886) +- WP-288: Implement Queue Filter for V3 apps (#883) +- WP-100: Display all Job Attributes in Jobs History > View Details (#868) +- WP-72: Workspace Search - Highlight matching search terms (#873) +- WP-273: App Category icon (#874) +- WP-32: Ability to see incomplete onboarding status in onboarding page. (#891) + +### Changed + +- WP-299: Add Data Files button dropdown needs minor adjustment in alignment (#878) +- WP-65: Data Files: Display full paths for concatenated breadcrumbs or filepaths (#866) +- WP-278: Data Files: Update Design of View Path Modal (#866) +- WP-279: Data Files: Support That Can Show All Text (#866) + +### Fixed +- WA-314: Input file fixes for hidden and FIXED types (#880) +- WP-66: Refactor DataFiles components to have more descriptive prop and variable names (#885 and #876) +- WP-109-remove-unused-django-fields (#887) +- Fix email confirmation for tickets (#879) ## [3.2.1] - 2023-10-05: Search and Target Path fixes @@ -959,7 +991,10 @@ WP-306: Fix target path regression (#871) ## [1.0.0] - 2020-02-28 v1.0.0 Production release as of Feb 28, 2020. -[unreleased]: https://github.com/TACC/Core-Portal/compare/v3.2.1...HEAD +[unreleased]: https://github.com/TACC/Core-Portal/compare/v3.3.2...HEAD +[3.3.2]: https://github.com/TACC/Core-Portal/releases/tag/v3.3.2 +[3.3.1]: https://github.com/TACC/Core-Portal/releases/tag/v3.3.1 +[3.3.0]: https://github.com/TACC/Core-Portal/releases/tag/v3.3.0 [3.2.1]: https://github.com/TACC/Core-Portal/releases/tag/v3.2.1 [3.2.0]: https://github.com/TACC/Core-Portal/releases/tag/v3.2.0 [3.1.2]: https://github.com/TACC/Core-Portal/releases/tag/v3.1.2 diff --git a/client/package-lock.json b/client/package-lock.json index f000bf735..98a48ba2e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -76,6 +76,7 @@ "stylelint": "^14.4.0", "stylelint-config-recommended": "^7.0.0", "stylelint-config-standard": "^25.0.0", + "timekeeper": "^2.3.1", "typescript": "^4.4.3", "vite": "^2.9.16", "weak-key": "^1.0.1" @@ -133,12 +134,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -184,13 +186,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.19.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" }, "engines": { @@ -297,9 +300,9 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" @@ -318,25 +321,25 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -461,30 +464,30 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -529,13 +532,13 @@ } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -543,9 +546,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -1926,33 +1929,33 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz", - "integrity": "sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.0", - "@babel/types": "^7.19.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1961,13 +1964,13 @@ } }, "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -3082,9 +3085,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, "engines": { "node": ">=6.0.0" @@ -3100,19 +3103,19 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@material-ui/core": { @@ -14617,6 +14620,12 @@ "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, + "node_modules/timekeeper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.3.1.tgz", + "integrity": "sha512-LeQRS7/4JcC0PgdSFnfUiStQEdiuySlCj/5SJ18D+T1n9BoY7PxKFfCwLulpHXoLUFr67HxBddQdEX47lDGx1g==", + "dev": true + }, "node_modules/tiny-invariant": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", @@ -15354,12 +15363,13 @@ } }, "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -15392,13 +15402,14 @@ } }, "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.19.0", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" } }, @@ -15475,9 +15486,9 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-explode-assignable-expression": { @@ -15490,22 +15501,22 @@ } }, "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-member-expression-to-functions": { @@ -15600,24 +15611,24 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { @@ -15650,20 +15661,20 @@ } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", - "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { @@ -16587,42 +16598,42 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.0.tgz", - "integrity": "sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.0", - "@babel/types": "^7.19.0", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -17394,9 +17405,9 @@ } }, "@jridgewell/resolve-uri": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.5.tgz", - "integrity": "sha512-VPeQ7+wH0itvQxnG+lIzWgkysKIr3L9sslimFW55rHMdGu/qCQ5z5h9zq4gI8uBtqkpHhsF4Z/OwExufUCThew==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true }, "@jridgewell/set-array": { @@ -17406,19 +17417,19 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.4.11", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz", - "integrity": "sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", "dev": true, "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@material-ui/core": { @@ -25859,6 +25870,12 @@ "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, + "timekeeper": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.3.1.tgz", + "integrity": "sha512-LeQRS7/4JcC0PgdSFnfUiStQEdiuySlCj/5SJ18D+T1n9BoY7PxKFfCwLulpHXoLUFr67HxBddQdEX47lDGx1g==", + "dev": true + }, "tiny-invariant": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", diff --git a/client/package.json b/client/package.json index db805dab0..8313ccab5 100644 --- a/client/package.json +++ b/client/package.json @@ -98,6 +98,7 @@ "stylelint": "^14.4.0", "stylelint-config-recommended": "^7.0.0", "stylelint-config-standard": "^25.0.0", + "timekeeper": "^2.3.1", "typescript": "^4.4.3", "vite": "^2.9.16", "weak-key": "^1.0.1" diff --git a/client/src/components/Applications/AppBrowser/AppBrowser.jsx b/client/src/components/Applications/AppBrowser/AppBrowser.jsx index f70890fc8..75535098e 100644 --- a/client/src/components/Applications/AppBrowser/AppBrowser.jsx +++ b/client/src/components/Applications/AppBrowser/AppBrowser.jsx @@ -90,7 +90,7 @@ const AppBrowser = () => { } > - + {app.label || app.appId} diff --git a/client/src/components/Applications/AppForm/AppForm.jsx b/client/src/components/Applications/AppForm/AppForm.jsx index 787dcf255..e90801b4a 100644 --- a/client/src/components/Applications/AppForm/AppForm.jsx +++ b/client/src/components/Applications/AppForm/AppForm.jsx @@ -150,6 +150,18 @@ const AdjustValuesWhenQueueChanges = ({ app }) => { }; const AppInfo = ({ app }) => { + const categoryDict = useSelector((state) => state.apps.categoryDict); + const getAppCategory = (appId) => { + for (const [cat, apps] of Object.entries(categoryDict)) { + if (apps.some((app) => app.appId === appId)) { + return cat; + } + } + return null; + }; + + const appCategory = getAppCategory(app.definition.id); + return (
{app.definition.label}
@@ -163,7 +175,7 @@ const AppInfo = ({ app }) => { target="_blank" rel="noreferrer noopener" > - {' '} + {' '} {app.definition.notes.label} Documentation ) : null} @@ -484,6 +496,7 @@ export const AppSchemaForm = ({ app }) => { targetDir: isTargetPathField(k) ? v : null, }; }) + .filter((v) => v) //filter nulls .reduce((acc, entry) => { // merge input field and targetPath fields into one. const key = getInputFieldFromTargetPathField(entry.name); @@ -691,11 +704,21 @@ export const AppSchemaForm = ({ app }) => { ) .map((q) => q.name) .sort() - .map((queueName) => ( - - )) + .map((queueName) => + app.definition.notes.queueFilter ? ( + app.definition.notes.queueFilter.includes( + queueName + ) && ( + + ) + ) : ( + + ) + ) .sort()} )} diff --git a/client/src/components/Applications/AppForm/AppForm.test.js b/client/src/components/Applications/AppForm/AppForm.test.js index d09920342..2a6b119df 100644 --- a/client/src/components/Applications/AppForm/AppForm.test.js +++ b/client/src/components/Applications/AppForm/AppForm.test.js @@ -1,5 +1,6 @@ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import '@testing-library/jest-dom/extend-expect'; import { Provider } from 'react-redux'; import configureStore from 'redux-mock-store'; import { BrowserRouter } from 'react-router-dom'; @@ -15,11 +16,16 @@ import { appTrayExpectedFixture, } from '../../../redux/sagas/fixtures/apptray.fixture'; import { initialAppState } from '../../../redux/reducers/apps.reducers'; -import { helloWorldAppFixture } from './fixtures/AppForm.app.fixture'; +import { + helloWorldAppFixture, + helloWorldAppSubmissionPayloadFixture, +} from './fixtures/AppForm.app.fixture'; import systemsFixture from '../../DataFiles/fixtures/DataFiles.systems.fixture'; import { projectsFixture } from '../../../redux/sagas/fixtures/projects.fixture'; import '@testing-library/jest-dom/extend-expect'; +import timekeeper from 'timekeeper'; +const frozenDate = '2023-10-01'; const mockStore = configureStore(); const initialMockState = { allocations: allocationsFixture, @@ -56,6 +62,11 @@ function renderAppSchemaFormComponent(store, app) { } describe('AppSchemaForm', () => { + beforeAll(() => { + // Lock Time + timekeeper.freeze(new Date(frozenDate)); + }); + it('renders the AppSchemaForm', async () => { const store = mockStore({ ...initialMockState, @@ -257,6 +268,162 @@ describe('AppSchemaForm', () => { expect(getByText(/Activate your Application Name license/)).toBeDefined(); }); }); + + it('job submission with file input mode FIXED', async () => { + const store = mockStore({ + ...initialMockState, + }); + + const { getByText, container } = renderAppSchemaFormComponent(store, { + ...helloWorldAppFixture, + definition: { + ...helloWorldAppFixture.definition, + jobAttributes: { + ...helloWorldAppFixture.definition.jobAttributes, + fileInputs: [ + { + name: 'File to copy', + description: 'A fixed file used by the app', + inputMode: 'FIXED', + autoMountLocal: true, + sourceUrl: + 'tapis://corral-tacc/tacc/aci/secure-test/rallyGolf.jpg', + targetPath: 'rallyGolf.jpg', + }, + ], + }, + }, + }); + const hiddenFileInput = container.querySelector( + 'input[name="fileInputs.File to copy"]' + ); + // FIXED fields are still shown in UI but not submitted. + expect(hiddenFileInput).toBeInTheDocument(); + + const submitButton = getByText(/Submit/); + fireEvent.click(submitButton); + const payload = { + ...helloWorldAppSubmissionPayloadFixture, + job: { + ...helloWorldAppSubmissionPayloadFixture.job, + name: 'hello-world-0.0.1_' + frozenDate + 'T00:00:00', + }, + }; + + await waitFor(() => { + expect(store.getActions()).toEqual([ + { type: 'GET_SYSTEM_MONITOR' }, + { type: 'SUBMIT_JOB', payload: payload }, + ]); + }); + }); + + it('job submission with file input hidden', async () => { + const store = mockStore({ + ...initialMockState, + }); + + const { getByText, container } = renderAppSchemaFormComponent(store, { + ...helloWorldAppFixture, + definition: { + ...helloWorldAppFixture.definition, + jobAttributes: { + ...helloWorldAppFixture.definition.jobAttributes, + fileInputs: [ + { + name: 'File to copy', + description: 'A fixed file used by the app', + inputMode: 'REQUIRED', + autoMountLocal: true, + sourceUrl: + 'tapis://corral-tacc/tacc/aci/secure-test/rallyGolf.jpg', + targetPath: 'rallyGolf.jpg', + notes: { + isHidden: true, + }, + }, + ], + }, + }, + }); + + const hiddenFileInput = container.querySelector( + 'input[name="fileInputs.File to copy"]' + ); + expect(hiddenFileInput).not.toBeInTheDocument(); + + const submitButton = getByText(/Submit/); + fireEvent.click(submitButton); + const payload = { + ...helloWorldAppSubmissionPayloadFixture, + job: { + ...helloWorldAppSubmissionPayloadFixture.job, + name: 'hello-world-0.0.1_' + frozenDate + 'T00:00:00', + }, + }; + + await waitFor(() => { + expect(store.getActions()).toEqual([ + { type: 'GET_SYSTEM_MONITOR' }, + { type: 'SUBMIT_JOB', payload: payload }, + ]); + }); + }); + + it('job submission with custom target path', async () => { + const store = mockStore({ + ...initialMockState, + }); + const { getByText, container } = renderAppSchemaFormComponent(store, { + ...helloWorldAppFixture, + definition: { + ...helloWorldAppFixture.definition, + notes: { + ...helloWorldAppFixture.definition.notes, + showTargetPath: true, + }, + }, + }); + + const fileInput = container.querySelector( + 'input[name="fileInputs.File to modify"]' + ); + const file = 'tapis://foo/bar.txt'; + const targetPathForFile = 'baz.txt'; + fireEvent.change(fileInput, { target: { value: file } }); + const targetPathInput = container.querySelector( + 'input[name="fileInputs._TargetPath_File to modify"]' + ); + fireEvent.change(targetPathInput, { target: { value: targetPathForFile } }); + + const submitButton = getByText(/Submit/); + fireEvent.click(submitButton); + const payload = { + ...helloWorldAppSubmissionPayloadFixture, + job: { + ...helloWorldAppSubmissionPayloadFixture.job, + fileInputs: [ + { + name: 'File to modify', + sourceUrl: file, + targetPath: targetPathForFile, + }, + ], + name: 'hello-world-0.0.1_' + frozenDate + 'T00:00:00', + }, + }; + + await waitFor(() => { + expect(store.getActions()).toEqual([ + { type: 'GET_SYSTEM_MONITOR' }, + { type: 'SUBMIT_JOB', payload: payload }, + ]); + }); + }); + + afterAll(() => { + timekeeper.reset(); + }); }); describe('AppDetail', () => { @@ -275,3 +442,58 @@ describe('AppDetail', () => { ).toBeDefined(); }); }); + +const mockAppWithQueueFilter = { + ...helloWorldAppFixture, + definition: { + ...helloWorldAppFixture.definition, + notes: { + ...helloWorldAppFixture.definition.notes, + queueFilter: ['rtx', 'small'], + }, + }, +}; + +const mockAppWithoutQueueFilter = { + ...helloWorldAppFixture, + definition: { + ...helloWorldAppFixture.definition, + notes: { + ...helloWorldAppFixture.definition.notes, + queueFilter: null, + }, + }, +}; + +describe('AppSchemaForm queueFilter tests', () => { + it('renders only the queues specified in the queueFilter', () => { + const { container } = renderAppSchemaFormComponent( + mockStore(initialMockState), + mockAppWithQueueFilter + ); + + const targetDropdown = container.querySelector( + 'select[name="execSystemLogicalQueue"]' + ); + const options = Array.from(targetDropdown.querySelectorAll('option')); + expect(options).toHaveLength(2); + expect(options[0].textContent).toBe('rtx'); + expect(options[1].textContent).toBe('small'); + }); + + it('renders all queues when no queueFilter is present', () => { + const { container } = renderAppSchemaFormComponent( + mockStore(initialMockState), + mockAppWithoutQueueFilter + ); + + const targetDropdown = container.querySelector( + 'select[name="execSystemLogicalQueue"]' + ); + const options = Array.from(targetDropdown.querySelectorAll('option')); + expect(options).toHaveLength(3); + expect(options[0].textContent).toBe('development'); + expect(options[1].textContent).toBe('rtx'); + expect(options[2].textContent).toBe('small'); + }); +}); diff --git a/client/src/components/Applications/AppForm/AppFormSchema.js b/client/src/components/Applications/AppForm/AppFormSchema.js index cfd772f48..b3f26ba09 100644 --- a/client/src/components/Applications/AppForm/AppFormSchema.js +++ b/client/src/components/Applications/AppForm/AppFormSchema.js @@ -106,11 +106,10 @@ const FormSchema = (app) => { app.definition.notes.showTargetPath ?? false; (app.definition.jobAttributes.fileInputs || []).forEach((i) => { const input = i; - /* TODOv3 consider hidden file inputs https://jira.tacc.utexas.edu/browse/WP-102 - if (input.name.startsWith('_') || !input.value.visible) { // TODOv3 visible or hidden - return; - } - */ + if (input.notes?.isHidden) { + return; + } + const field = { label: input.name, description: input.description, diff --git a/client/src/components/Applications/AppForm/fixtures/AppForm.app.fixture.js b/client/src/components/Applications/AppForm/fixtures/AppForm.app.fixture.js index 5aa718ba4..1458448ea 100644 --- a/client/src/components/Applications/AppForm/fixtures/AppForm.app.fixture.js +++ b/client/src/components/Applications/AppForm/fixtures/AppForm.app.fixture.js @@ -237,3 +237,49 @@ export const helloWorldAppFixture = { type: null, }, }; + +export const helloWorldAppSubmissionPayloadFixture = { + job: { + fileInputs: [], + parameterSet: { + appArgs: [ + { + name: 'Greeting', + arg: 'hello', + }, + { + name: 'Target', + arg: 'world', + }, + { + name: 'Sleep Time', + arg: '30', + }, + ], + containerArgs: [], + schedulerOptions: [ + { + name: 'TACC Allocation', + description: 'The TACC allocation associated with this job execution', + include: true, + arg: '-A TACC-ACI', + }, + ], + envVariables: [], + }, + name: 'hello-world-0.0.1', + nodeCount: 1, + coresPerNode: 1, + maxMinutes: 10, + archiveSystemId: 'frontera', + archiveSystemDir: + 'HOST_EVAL($HOME)/tapis-jobs-archive/${JobCreateDate}/${JobName}-${JobUUID}', + archiveOnAppError: true, + appId: 'hello-world', + appVersion: '0.0.1', + execSystemId: 'frontera', + execSystemLogicalQueue: 'development', + }, + licenseType: null, + isInteractive: false, +}; diff --git a/client/src/components/DataFiles/CombinedBreadcrumbs/CombinedBreadcrumbs.jsx b/client/src/components/DataFiles/CombinedBreadcrumbs/CombinedBreadcrumbs.jsx new file mode 100644 index 000000000..7f5ce80c5 --- /dev/null +++ b/client/src/components/DataFiles/CombinedBreadcrumbs/CombinedBreadcrumbs.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import DataFilesBreadcrumbs from '../DataFilesBreadcrumbs/DataFilesBreadcrumbs.jsx'; +import BreadcrumbsDropdown from '../DataFilesDropdown/DataFilesDropdown.jsx'; +import styles from './CombinedBreadcrumbs.module.scss'; + +const CombinedBreadcrumbs = (props) => { + return ( +
+ + +
+ ); +}; + +CombinedBreadcrumbs.propTypes = { + api: PropTypes.string.isRequired, + scheme: PropTypes.string.isRequired, + system: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + section: PropTypes.string.isRequired, + isPublic: PropTypes.bool, + className: PropTypes.string, +}; + +CombinedBreadcrumbs.defaultProps = { + isPublic: false, + className: '', +}; + +export default CombinedBreadcrumbs; diff --git a/client/src/components/DataFiles/CombinedBreadcrumbs/CombinedBreadcrumbs.module.scss b/client/src/components/DataFiles/CombinedBreadcrumbs/CombinedBreadcrumbs.module.scss new file mode 100644 index 000000000..7898fea1f --- /dev/null +++ b/client/src/components/DataFiles/CombinedBreadcrumbs/CombinedBreadcrumbs.module.scss @@ -0,0 +1,5 @@ +.combined-breadcrumbs { + display: flex; + align-items: center; + gap: 1rem; +} diff --git a/client/src/components/DataFiles/DataFiles.jsx b/client/src/components/DataFiles/DataFiles.jsx index 650175100..7d67a0596 100644 --- a/client/src/components/DataFiles/DataFiles.jsx +++ b/client/src/components/DataFiles/DataFiles.jsx @@ -15,7 +15,7 @@ import { useFileListing, useSystems } from 'hooks/datafiles'; import DataFilesToolbar from './DataFilesToolbar/DataFilesToolbar'; import DataFilesListing from './DataFilesListing/DataFilesListing'; import DataFilesSidebar from './DataFilesSidebar/DataFilesSidebar'; -import DataFilesBreadcrumbs from './DataFilesBreadcrumbs/DataFilesBreadcrumbs'; +import CombinedBreadcrumbs from './CombinedBreadcrumbs/CombinedBreadcrumbs'; import DataFilesModals from './DataFilesModals/DataFilesModals'; import DataFilesProjectsList from './DataFilesProjectsList/DataFilesProjectsList'; import DataFilesProjectFileListing from './DataFilesProjectFileListing/DataFilesProjectFileListing'; @@ -138,7 +138,7 @@ const DataFiles = () => { listingParams.system === noPHISystem ? 'UNPROTECTED' : 'DATA' } header={ - {children} @@ -46,7 +51,7 @@ const BreadcrumbLink = ({ return ( @@ -120,6 +125,22 @@ const DataFilesBreadcrumbs = ({ const paths = []; const pathComps = []; + const dispatch = useDispatch(); + + const fileData = { + system: system, + path: path, + }; + + const openFullPathModal = (e) => { + e.stopPropagation(); + e.preventDefault(); + dispatch({ + type: 'DATA_FILES_TOGGLE_MODAL', + payload: { operation: 'showpath', props: { file: fileData } }, + }); + }; + const { fetchSelectedSystem } = useSystems(); const selectedSystem = fetchSelectedSystem({ scheme, system, path }); @@ -155,52 +176,29 @@ const DataFilesBreadcrumbs = ({ } }, ''); + const fullPath = paths.slice(-1); + const currentDirectory = pathComps.slice(-1); + return ( -
- {scheme === 'projects' && ( - <> - {' '} - {system && `/ `} - +
+
+ {currentDirectory.length === 0 ? ( + + {truncateMiddle(systemName || 'Shared Workspaces', 30)} + + ) : ( + currentDirectory.map((pathComp, i) => { + if (i === fullPath.length - 1) { + return {truncateMiddle(pathComp, 30)}; + } + }) + )} +
+ {systemName && api === 'tapis' && ( + )} - - <>{systemName} - - {pathComps.map((pathComp, i) => { - if (i < paths.length - 2) { - return ' /... '; - } - if (i === paths.length - 1) { - return / {pathComp}; - } - return ( - - {' '} - /{' '} - - <>{pathComp} - - - ); - })}
); }; diff --git a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.scss b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.scss index 9e9673332..e06f9db5c 100644 --- a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.scss +++ b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.scss @@ -1,9 +1,12 @@ @import '../../../styles/tools/mixins.scss'; +@import '../../../styles/components/dropdown-menu.css'; .breadcrumbs { /* ... */ @include truncate-with-ellipsis; margin-right: 2em; + display: flex; + align-items: center; } .breadcrumb-link, .breadcrumb-link:hover { @@ -34,3 +37,74 @@ max-width: 700px; } } + +#path-button-wrapper { + padding-left: var(--horizontal-buffer); +} + +/* Nested to prevent styles from affecting CMS header dropdown */ +/* HACK: Using ID to increase specificity (until source of problem is fixed) */ +/* HELP: Why does DataFilesSidebar not need such specificity? */ +/* .go-to-button-dropdown { */ +#go-to-button-dropdown { + /* To fix menu not showing */ + /* HELP: Why does DataFilesSidebar not need this fix? */ + .dropdown-menu { + opacity: 1 !important; + pointer-events: auto !important; + } + /* To restyle */ + .dropdown-menu { + margin-top: 38px; + } + .dropdown-menu::before, + .dropdown-menu::after { + left: 23px; + margin-left: 0; + } + .dropdown-menu::after { + top: -9px; + } + + .dropdown-item { + display: inline-block; + } +} + +.breadcrumb-container { + display: flex; + align-items: center; +} + +.truncate { + display: inline-block; + max-width: 600px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding-left: 20px; +} + +#go-to-button-dropdown .complex-dropdown-item-root, +.complex-dropdown-item-project { + display: flex !important; +} + +#go-to-button-dropdown .link-hover:hover { + text-decoration: none; +} + +.multiline-menu-item-wrapper { + display: inline-block; + padding-left: 5px; + line-height: 1.1em; + small { + display: block; + color: var(--global-color-primary--x-dark); + } +} + +.breadcrumbs .vertical-align-separator { + margin-right: 2px; + margin-left: 10px; +} diff --git a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.test.js b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.test.js index 7520d2f06..77f0580b0 100644 --- a/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.test.js +++ b/client/src/components/DataFiles/DataFilesBreadcrumbs/DataFilesBreadcrumbs.test.js @@ -7,6 +7,7 @@ import systemsFixture from '../fixtures/DataFiles.systems.fixture'; import filesFixture from '../fixtures/DataFiles.files.fixture'; import { initialSystemState } from '../../../redux/reducers/datafiles.reducers'; import { projectsFixture } from '../../../redux/sagas/fixtures/projects.fixture'; +import { fireEvent } from '@testing-library/react'; const mockStore = configureStore(); @@ -16,8 +17,7 @@ describe('DataFilesBreadcrumbs', () => { systems: systemsFixture, projects: projectsFixture, }); - const history = createMemoryHistory(); - const { getByText, debug } = renderComponent( + const { getByText } = renderComponent( { createMemoryHistory() ); - expect(getByText(/My Data \(Frontera\)/)).toBeDefined(); - expect( - getByText(/My Data \(Frontera\)/) - .closest('a') - .getAttribute('href') - ).toEqual( - '/workbench/data/tapis/private/frontera.home.username/home/username/' - ); - expect(getByText(/the/).closest('a').getAttribute('href')).toEqual( - '/workbench/data/tapis/private/frontera.home.username/home/username/path/to/the/' - ); - expect(getByText(/files/).closest('a')).toBeNull(); + // Check if the last part of the path is rendered as text + const filesText = getByText('files'); + expect(filesText).toBeDefined(); + expect(filesText.closest('a')).toBeNull(); }); it('renders correct breadcrumbs when in root of system', () => { const store = mockStore({ systems: systemsFixture, }); - const history = createMemoryHistory(); - const { getByText, debug } = renderComponent( + const { getByText } = renderComponent( { createMemoryHistory() ); + // Check if the system name is rendered as text when in the root of the system expect(getByText('Frontera')).toBeDefined(); - expect(getByText('Frontera').closest('a').getAttribute('href')).toEqual( - '/workbench/data/tapis/private/frontera.home.username/' - ); }); - it('render breadcrumbs with initial empty systems', () => { + it('render breadcrumbs for projects', () => { const store = mockStore({ - systems: initialSystemState, + systems: systemsFixture, projects: projectsFixture, + files: filesFixture, }); - const history = createMemoryHistory(); - const { getByText, debug } = renderComponent( + const { getByText } = renderComponent( { createMemoryHistory() ); - expect(getByText(/Frontera/)).toBeDefined(); - expect( - getByText(/Frontera/) - .closest('a') - .getAttribute('href') - ).toEqual('/workbench/data/tapis/private/frontera.home.username/'); + // Check if the last part of the path is rendered as text for projects + const filesText = getByText('files'); + expect(filesText).toBeDefined(); + expect(filesText.closest('a')).toBeNull(); }); - it('render breadcrumbs for projects', () => { + it('dispatches action to open full path modal on button click', () => { const store = mockStore({ systems: systemsFixture, projects: projectsFixture, - files: filesFixture, }); - const history = createMemoryHistory(); - const { getByText, debug } = renderComponent( + const { getByText } = renderComponent( , store, createMemoryHistory() ); + const viewFullPathButton = getByText('View Full Path'); + fireEvent.click(viewFullPathButton); + + const actions = store.getActions(); + const expectedActions = { + type: 'DATA_FILES_TOGGLE_MODAL', + payload: { + operation: 'showpath', + props: { + file: { + system: 'frontera.home.username', + path: '/home/username/path/to/the/files', + }, + }, + }, + }; + expect(actions).toContainEqual(expectedActions); + }); - expect(getByText(/Shared Workspaces/)).toBeDefined(); - expect( - getByText(/Shared Workspaces/) - .closest('a') - .getAttribute('href') - ).toEqual('/workbench/data/tapis/projects/'); + it('renders pathComp, which is current directory', () => { + const store = mockStore({ + systems: systemsFixture, + }); + const history = createMemoryHistory(); + const { getByText } = renderComponent( + , + store, + createMemoryHistory() + ); + const pathComp = getByText('files'); + expect(pathComp).toBeDefined(); }); }); diff --git a/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx new file mode 100644 index 000000000..246f72788 --- /dev/null +++ b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.jsx @@ -0,0 +1,153 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import { Link, useLocation } from 'react-router-dom'; +import { Button } from '_common'; +import { + DropdownToggle, + DropdownMenu, + DropdownItem, + ButtonDropdown, +} from 'reactstrap'; +import { useSystemDisplayName, useSystems } from 'hooks/datafiles'; +import '../DataFilesBreadcrumbs/DataFilesBreadcrumbs.scss'; + +const BreadcrumbsDropdown = ({ + api, + scheme, + system, + path, + section, + isPublic, +}) => { + const paths = []; + const [dropdownOpen, setDropdownOpen] = useState(false); + const toggleDropdown = () => setDropdownOpen(!dropdownOpen); + + const location = useLocation(); + const pathParts = location.pathname.split('/'); + const projectId = pathParts.includes('projects') + ? pathParts[pathParts.indexOf('projects') + 1] + : null; + + const handleNavigation = (targetPath) => { + const basePath = isPublic ? '/public-data' : '/workbench/data'; + let url; + + if (scheme === 'projects' && targetPath === systemName) { + url = `${basePath}/${api}/projects/${projectId}/`; + } else if (scheme === 'projects' && !targetPath) { + url = `${basePath}/${api}/projects/`; + } else if (api === 'googledrive' && !targetPath) { + url = `${basePath}/${api}/${scheme}/${system}/`; + } else if (api === 'tapis' && scheme !== 'projects' && !targetPath) { + url = `${basePath}/${api}/${scheme}/${system}/`; + } else { + url = `${basePath}/${api}/${scheme}/${system}${targetPath}/`; + } + + return url; + }; + + const { fetchSelectedSystem } = useSystems(); + const selectedSystem = fetchSelectedSystem({ scheme, system, path }); + const systemName = useSystemDisplayName({ scheme, system, path }); + const homeDir = selectedSystem?.homeDir; + const isSystemRootPath = !path + .replace(/^\/+/, '') + .startsWith(homeDir?.replace(/^\/+/, '')); + const startingPath = isSystemRootPath ? '' : homeDir; + + const pathComponents = path.split('/').filter((x) => !!x); + const startingPathComponents = startingPath.split('/').filter((x) => !!x); + const overlapIndex = pathComponents.findIndex( + (component, index) => startingPathComponents[index] !== component + ); + + let currentPath = startingPath; + pathComponents.slice(overlapIndex).forEach((component) => { + currentPath = `${currentPath}/${component}`; + paths.push(currentPath); + }); + + const fullPath = paths.reverse(); + const displayPaths = + scheme === 'projects' ? [...fullPath, systemName] : fullPath; + const sliceStart = scheme === 'projects' && systemName ? 0 : 1; + return ( +
+ + Go to ... + + {displayPaths + .slice(sliceStart, displayPaths.length) + .map((path, index) => { + const folderName = path.split('/').pop(); + return ( + + + + + {folderName.length > 20 + ? folderName.substring(0, 20) + : folderName} + {scheme === 'projects' && path === systemName && ( + Project Name + )} + + + + ); + })} + + + + + + {scheme === 'projects' + ? 'Shared Workspaces' + : systemName || 'Shared Workspaces'} + {homeDir ? Root : null} + + + + + +
+ ); +}; + +BreadcrumbsDropdown.propTypes = { + api: PropTypes.string.isRequired, + scheme: PropTypes.string.isRequired, + system: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + section: PropTypes.string, + isPublic: PropTypes.bool, +}; + +BreadcrumbsDropdown.defaultProps = { + isPublic: false, +}; + +export default BreadcrumbsDropdown; diff --git a/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.test.js b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.test.js new file mode 100644 index 000000000..c422af617 --- /dev/null +++ b/client/src/components/DataFiles/DataFilesDropdown/DataFilesDropdown.test.js @@ -0,0 +1,58 @@ +import React from 'react'; +import { fireEvent } from '@testing-library/react'; +import configureMockStore from 'redux-mock-store'; +import renderComponent from 'utils/testing'; +import systemsFixture from '../fixtures/DataFiles.systems.fixture'; +import BreadcrumbsDropdown from './DataFilesDropdown'; + +const mockStore = configureMockStore(); + +describe('BreadcrumbsDropdown', () => { + it('renders "Go to ..." dropdown and can be toggled', () => { + const store = mockStore({ + systems: systemsFixture, + }); + + const { getByText } = renderComponent( + , + store + ); + + const dropdownToggle = getByText('Go to ...'); + expect(dropdownToggle).toBeDefined(); + + // Toggle dropdown + fireEvent.click(dropdownToggle); + + // Now, dropdown content should be visible + expect(getByText('to')).toBeDefined(); + expect(getByText('path')).toBeDefined(); + }); + + it('renders root path correctly', () => { + const store = mockStore({ + systems: systemsFixture, + }); + + const { getByText } = renderComponent( + , + store + ); + + const dropdownToggle = getByText('Go to ...'); + fireEvent.click(dropdownToggle); + + // Check if the root path is rendered correctly + expect(getByText('Frontera')).toBeDefined(); + }); +}); diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesCopyModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesCopyModal.jsx index bebffdf3f..c4cd26d68 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesCopyModal.jsx +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesCopyModal.jsx @@ -142,7 +142,7 @@ const DataFilesCopyModal = React.memo(() => { Destination { const systems = useSelector( @@ -21,6 +22,7 @@ const DataFilesSelectModal = ({ isOpen, toggle, onSelect }) => { (state) => state.files.params.modal, shallowEqual ); + const selectRef = React.useRef(); const onOpened = () => { const systemParams = { @@ -29,6 +31,7 @@ const DataFilesSelectModal = ({ isOpen, toggle, onSelect }) => { system: systems.filter((s) => !s.hidden)[0].system, path: systems.filter((s) => !s.hidden)[0]?.homeDir || '', }; + dispatch({ type: 'FETCH_FILES_MODAL', payload: { ...systemParams, section: 'modal' }, @@ -40,6 +43,10 @@ const DataFilesSelectModal = ({ isOpen, toggle, onSelect }) => { props: {}, }, }); + dispatch({ + type: 'FETCH_SYSTEM_DEFINITION', + payload: systemParams.system, + }); }; const selectCallback = (system, path) => { onSelect(system, path); @@ -72,7 +79,7 @@ const DataFilesSelectModal = ({ isOpen, toggle, onSelect }) => { Select Input { /> )}
+
{showProjects ? ( diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx b/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx index 41b5b5979..0b49d5840 100644 --- a/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.jsx @@ -2,7 +2,7 @@ import React, { useEffect } from 'react'; import { useSelector, useDispatch, shallowEqual } from 'react-redux'; import { Modal, ModalHeader, ModalBody } from 'reactstrap'; import { TextCopyField } from '_common'; -import DataFilesBreadcrumbs from '../DataFilesBreadcrumbs/DataFilesBreadcrumbs'; +import styles from './DataFilesShowPathModal.module.scss'; const DataFilesShowPathModal = React.memo(() => { const dispatch = useDispatch(); @@ -11,6 +11,22 @@ const DataFilesShowPathModal = React.memo(() => { shallowEqual ); + const modalParams = useSelector( + (state) => state.files.params.modal, + shallowEqual + ); + + const { api: modalApi } = modalParams; + + useEffect(() => { + if (modalApi === 'tapis' && modalParams.system) { + dispatch({ + type: 'FETCH_SYSTEM_DEFINITION', + payload: modalParams.system, + }); + } + }, [modalParams, dispatch]); + useEffect(() => { if (params.api === 'tapis' && params.system) { dispatch({ @@ -54,37 +70,39 @@ const DataFilesShowPathModal = React.memo(() => { toggle={toggle} className="dataFilesModal" > - - Pathnames for {file.name} + + View Full Path - -
- {params.api === 'tapis' && definition && ( - <> -
Storage Host
-
{definition.host}
-
Storage Path
- - )} - {params.api === 'googledrive' && ( - <> -
Storage Location
-
Google Drive
- - )} -
+ {(params.api === 'tapis' || modalApi === 'tapis') && definition && ( + <> + Storage Host + + {definition.host} + + Storage Path -
-
+ + )} + {params.api === 'googledrive' && ( + <> + + Storage Location + + Google Drive + + )}
); diff --git a/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.module.scss b/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.module.scss new file mode 100644 index 000000000..598be3708 --- /dev/null +++ b/client/src/components/DataFiles/DataFilesModals/DataFilesShowPathModal.module.scss @@ -0,0 +1,24 @@ +.custom-modal-header { + padding-left: 15px; +} + +.custom-textcopyfield { + margin-top: 0; +} + +.storage-host, +.storage-path, +.storage-location { + font-weight: bold; + display: block; + margin-top: 10px; + margin-left: 15px; +} + +.storage-path { + margin-bottom: 0; +} + +.storage-values { + margin-left: 15px; +} diff --git a/client/src/components/DataFiles/DataFilesProjectMembers/DataFilesProjectMembers.jsx b/client/src/components/DataFiles/DataFilesProjectMembers/DataFilesProjectMembers.jsx index b8bad0f9e..839d7511c 100644 --- a/client/src/components/DataFiles/DataFilesProjectMembers/DataFilesProjectMembers.jsx +++ b/client/src/components/DataFiles/DataFilesProjectMembers/DataFilesProjectMembers.jsx @@ -286,7 +286,7 @@ const DataFilesProjectMembers = ({ }; DataFilesProjectMembers.propTypes = { - projectId: PropTypes.string, + projectId: PropTypes.string.isRequired, members: PropTypes.arrayOf( PropTypes.shape({ username: PropTypes.string, diff --git a/client/src/components/DataFiles/DataFilesProjectsList/DataFilesProjectsList.jsx b/client/src/components/DataFiles/DataFilesProjectsList/DataFilesProjectsList.jsx index 93c4f3181..f90f80791 100644 --- a/client/src/components/DataFiles/DataFilesProjectsList/DataFilesProjectsList.jsx +++ b/client/src/components/DataFiles/DataFilesProjectsList/DataFilesProjectsList.jsx @@ -85,7 +85,9 @@ const DataFilesProjectsList = ({ modal }) => { Header: 'ID', headerStyle: { textAlign: 'left' }, accessor: 'name', - Cell: (el) => {el.value.split('-').slice(-1)[0]}, + Cell: (el) => ( + {el.value ? el.value.split('-').slice(-1)[0] : ''} + ), }, ]; diff --git a/client/src/components/DataFiles/DataFilesSidebar/DataFilesSidebar.scss b/client/src/components/DataFiles/DataFilesSidebar/DataFilesSidebar.scss index 9f4fac877..b2c09a31a 100644 --- a/client/src/components/DataFiles/DataFilesSidebar/DataFilesSidebar.scss +++ b/client/src/components/DataFiles/DataFilesSidebar/DataFilesSidebar.scss @@ -1,3 +1,5 @@ +@import '../../../styles/components/dropdown-menu.css'; + .data-files-btn { background-color: var(--global-color-accent--normal); border-color: var(--global-color-accent--normal); @@ -19,20 +21,19 @@ padding-top: 20px; } -/* HACK: Quick solution to prevent styles from cascading into header dropdown */ +/* Nested to prevent styles from affecting CMS header dropdown */ .data-files-sidebar { .dropdown-menu { border-color: var(--global-color-accent--normal); border-radius: 0; - margin-top: 11px; - padding: 0; + margin: 11px 3px 0; width: 200px; vertical-align: top; } .dropdown-menu::before { position: absolute; top: -10px; - left: 65px; + left: 67px; border-right: 10px solid transparent; border-bottom: 10px solid var(--global-color-accent--normal); border-left: 10px solid transparent; @@ -40,9 +41,8 @@ content: ''; } .dropdown-menu::after { - position: absolute; top: -9px; - left: 66px; + left: 68px; border-right: 9px solid transparent; border-bottom: 9px solid #ffffff; border-left: 9px solid transparent; diff --git a/client/src/components/DataFiles/DataFilesSystemSelector/DataFilesSystemSelector.jsx b/client/src/components/DataFiles/DataFilesSystemSelector/DataFilesSystemSelector.jsx index 55a477207..019a21ab3 100644 --- a/client/src/components/DataFiles/DataFilesSystemSelector/DataFilesSystemSelector.jsx +++ b/client/src/components/DataFiles/DataFilesSystemSelector/DataFilesSystemSelector.jsx @@ -48,6 +48,10 @@ const DataFilesSystemSelector = ({ section, }, }); + dispatch({ + type: 'FETCH_SYSTEM_DEFINITION', + payload: system.system, + }); dispatch({ type: 'DATA_FILES_SET_MODAL_PROPS', payload: { diff --git a/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx b/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx index 23c776311..63b22155a 100644 --- a/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx +++ b/client/src/components/DataFiles/DataFilesToolbar/DataFilesToolbar.jsx @@ -13,6 +13,7 @@ export const ToolbarButton = ({ text, iconName, onClick, disabled }) => { - -
- + {displayField === 'textarea' ? ( + <> +