From e917ee97e839d4760246154edf1653d1eec864cb Mon Sep 17 00:00:00 2001 From: Prithul Sarker <43958517+prithuls@users.noreply.github.com> Date: Thu, 3 Aug 2023 13:07:35 -0500 Subject: [PATCH 01/16] Add targetPath to job.fileInputs targetPath from app definition is added to the job submission under job.fileInputs --- client/src/components/Applications/AppForm/AppForm.jsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/client/src/components/Applications/AppForm/AppForm.jsx b/client/src/components/Applications/AppForm/AppForm.jsx index 0e7005bf7..40edc6b9d 100644 --- a/client/src/components/Applications/AppForm/AppForm.jsx +++ b/client/src/components/Applications/AppForm/AppForm.jsx @@ -461,7 +461,13 @@ export const AppSchemaForm = ({ app }) => { job.fileInputs = Object.entries(job.fileInputs) .map(([k, v]) => { - return { name: k, sourceUrl: v }; + return { + name: k, + sourceUrl: v, + targetPath: app.definition.jobAttributes.fileInputs.find( + (file) => file.name === k + ).targetPath, + }; }) .filter((fileInput) => fileInput.sourceUrl); // filter out any empty values From e6665858480a8431c2dae16e77846a2dcbb7ed72 Mon Sep 17 00:00:00 2001 From: Chandra Y Date: Tue, 12 Sep 2023 17:48:14 -0500 Subject: [PATCH 02/16] Add target path for every file input --- .../Applications/AppForm/AppForm.jsx | 63 +++++++++++++------ .../Applications/AppForm/AppFormSchema.js | 26 ++++++++ .../Applications/AppForm/AppFormUtils.js | 35 +++++++++++ 3 files changed, 104 insertions(+), 20 deletions(-) diff --git a/client/src/components/Applications/AppForm/AppForm.jsx b/client/src/components/Applications/AppForm/AppForm.jsx index 40edc6b9d..17b794dbf 100644 --- a/client/src/components/Applications/AppForm/AppForm.jsx +++ b/client/src/components/Applications/AppForm/AppForm.jsx @@ -19,10 +19,13 @@ import { Link } from 'react-router-dom'; import { getSystemName } from 'utils/systems'; import FormSchema from './AppFormSchema'; import { + isTargetPathField, + getInputFieldFromTargetPathField, getQueueMaxMinutes, getMaxMinutesValidation, getNodeCountValidation, getCoresPerNodeValidation, + getTargetPathFieldName, updateValuesForQueue, } from './AppFormUtils'; import DataFilesSelectModal from '../../DataFiles/DataFilesModals/DataFilesSelectModal'; @@ -459,17 +462,30 @@ export const AppSchemaForm = ({ app }) => { onSubmit={(values, { setSubmitting, resetForm }) => { const job = cloneDeep(values); - job.fileInputs = Object.entries(job.fileInputs) - .map(([k, v]) => { - return { - name: k, - sourceUrl: v, - targetPath: app.definition.jobAttributes.fileInputs.find( - (file) => file.name === k - ).targetPath, - }; - }) - .filter((fileInput) => fileInput.sourceUrl); // filter out any empty values + // Tranform input field values into format that jobs service wants + job.fileInputs = Object.values( + Object.entries(job.fileInputs) + .map(([k, v]) => { + return { + name: k, + sourceUrl: !isTargetPathField(k) ? v : null, + targetDir: isTargetPathField(k) ? v : null, + }; + }) + .filter((fileInput) => fileInput.sourceUrl || fileInput.targetDir) // filter out any empty values + .reduce((acc, entry) => { // merge input field and targetPath fields into one. + const key = getInputFieldFromTargetPathField(entry.name); + if (!acc[key]) { + acc[key] = {}; + } + acc[key]['name'] = key; + acc[key]['sourceUrl'] = + acc[key]['sourceUrl'] ?? entry.sourceUrl; + acc[key]['targetPath'] = + acc[key]['targetPath'] ?? entry.targetDir; + return acc; + }, {}) + ).flat(); job.parameterSet = Object.assign( {}, @@ -550,16 +566,23 @@ export const AppSchemaForm = ({ app }) => { {Object.entries(appFields.fileInputs).map( ([name, field]) => { - // TODOv3 handle fileInputArrays https://jira.tacc.utexas.edu/browse/TV3-81 + // TODOv3 handle fileInputArrays https://jira.tacc.utexas.edu/browse/WP-81 return ( - + isTargetPathField(name)? + : + ); } )} diff --git a/client/src/components/Applications/AppForm/AppFormSchema.js b/client/src/components/Applications/AppForm/AppFormSchema.js index ab9a98d09..310b61c4a 100644 --- a/client/src/components/Applications/AppForm/AppFormSchema.js +++ b/client/src/components/Applications/AppForm/AppFormSchema.js @@ -1,4 +1,7 @@ import * as Yup from 'yup'; +import { + getTargetPathFieldName +} from './AppFormUtils'; const FormSchema = (app) => { const appFields = { @@ -131,6 +134,29 @@ const FormSchema = (app) => { input.sourceUrl === null || typeof input.sourceUrl === 'undefined' ? '' : input.sourceUrl; + + // Add targetDir for all sourceUrl + const targetPathName = getTargetPathFieldName(input.name); + appFields.schema.fileInputs[targetPathName] = Yup.string(); + appFields.schema.fileInputs[targetPathName] = appFields.schema.fileInputs[ + targetPathName + ].matches( + /^tapis:\/\//g, + "Input file Target Directory must be a valid Tapis URI, starting with 'tapis://'" + ); + + appFields.schema.fileInputs[targetPathName] = false; + appFields.fileInputs[targetPathName] = { + label: 'Target Path for ' + input.name, + description: 'The target path is the location to which data are copied from the input. Empty target path or \'*\' indicates, the simple directory or file name from the input path is automatically assign to the target path.', + required: false, + readOnly: false, + type: 'text' + }; + appFields.defaults.fileInputs[targetPathName] = + input.targetPath === null || typeof input.targetPath === 'undefined' + ? '*' + : input.targetPath; }); return appFields; }; diff --git a/client/src/components/Applications/AppForm/AppFormUtils.js b/client/src/components/Applications/AppForm/AppFormUtils.js index 812fe3379..b0469348d 100644 --- a/client/src/components/Applications/AppForm/AppFormUtils.js +++ b/client/src/components/Applications/AppForm/AppFormUtils.js @@ -1,6 +1,8 @@ import * as Yup from 'yup'; import { getSystemName } from 'utils/systems'; +export const TARGET_PATH_FIELD_PREFIX = '_TargetPath_'; + export const getQueueMaxMinutes = (app, queueName) => { return app.exec_sys.batchLogicalQueues.find((q) => q.name === queueName) .maxMinutes; @@ -165,3 +167,36 @@ export const updateValuesForQueue = (app, values) => { return updatedValues; }; + +/** + * Get the field name used for target path in AppForm + * + * @function + * @param {String} inputFieldName + * @returns {String} field Name prefixed with target path + */ +export const getTargetPathFieldName = (inputFieldName) => { + return TARGET_PATH_FIELD_PREFIX + inputFieldName; +}; + +/** + * Whether a field name is a system defined field for Target Path + * + * @function + * @param {String} inputFieldName + * @returns {String} field Name suffixed with target path + */ +export const isTargetPathField = (inputFieldName) => { + return inputFieldName && inputFieldName.startsWith(TARGET_PATH_FIELD_PREFIX); +}; + +/** + * From target path field name, derive the original input field name. + * + * @function + * @param {String} targetPathFieldName + * @returns {String} actual field name + */ +export const getInputFieldFromTargetPathField = (targetPathFieldName) => { + return targetPathFieldName.replace(TARGET_PATH_FIELD_PREFIX,''); +}; From 10434980e302c8610751c335c6c3a9bcdf5d85de Mon Sep 17 00:00:00 2001 From: Chandra Y Date: Tue, 12 Sep 2023 19:31:26 -0500 Subject: [PATCH 03/16] Handle empty targetPath input and linter fix --- .../Applications/AppForm/AppForm.jsx | 41 ++++++++++--------- .../Applications/AppForm/AppFormSchema.js | 14 +++---- .../Applications/AppForm/AppFormUtils.js | 25 ++++++++++- 3 files changed, 52 insertions(+), 28 deletions(-) diff --git a/client/src/components/Applications/AppForm/AppForm.jsx b/client/src/components/Applications/AppForm/AppForm.jsx index 17b794dbf..650307735 100644 --- a/client/src/components/Applications/AppForm/AppForm.jsx +++ b/client/src/components/Applications/AppForm/AppForm.jsx @@ -19,13 +19,13 @@ import { Link } from 'react-router-dom'; import { getSystemName } from 'utils/systems'; import FormSchema from './AppFormSchema'; import { + checkAndSetDefaultTargetPath, isTargetPathField, getInputFieldFromTargetPathField, getQueueMaxMinutes, getMaxMinutesValidation, getNodeCountValidation, getCoresPerNodeValidation, - getTargetPathFieldName, updateValuesForQueue, } from './AppFormUtils'; import DataFilesSelectModal from '../../DataFiles/DataFilesModals/DataFilesSelectModal'; @@ -469,11 +469,14 @@ export const AppSchemaForm = ({ app }) => { return { name: k, sourceUrl: !isTargetPathField(k) ? v : null, - targetDir: isTargetPathField(k) ? v : null, + targetDir: isTargetPathField(k) + ? checkAndSetDefaultTargetPath(v) + : null, }; }) .filter((fileInput) => fileInput.sourceUrl || fileInput.targetDir) // filter out any empty values - .reduce((acc, entry) => { // merge input field and targetPath fields into one. + .reduce((acc, entry) => { + // merge input field and targetPath fields into one. const key = getInputFieldFromTargetPathField(entry.name); if (!acc[key]) { acc[key] = {}; @@ -567,22 +570,22 @@ export const AppSchemaForm = ({ app }) => { {Object.entries(appFields.fileInputs).map( ([name, field]) => { // TODOv3 handle fileInputArrays https://jira.tacc.utexas.edu/browse/WP-81 - return ( - isTargetPathField(name)? - : - + return isTargetPathField(name) ? ( + + ) : ( + ); } )} diff --git a/client/src/components/Applications/AppForm/AppFormSchema.js b/client/src/components/Applications/AppForm/AppFormSchema.js index 310b61c4a..a2e9c53b2 100644 --- a/client/src/components/Applications/AppForm/AppFormSchema.js +++ b/client/src/components/Applications/AppForm/AppFormSchema.js @@ -1,6 +1,7 @@ import * as Yup from 'yup'; import { - getTargetPathFieldName + checkAndSetDefaultTargetPath, + getTargetPathFieldName, } from './AppFormUtils'; const FormSchema = (app) => { @@ -134,7 +135,7 @@ const FormSchema = (app) => { input.sourceUrl === null || typeof input.sourceUrl === 'undefined' ? '' : input.sourceUrl; - + // Add targetDir for all sourceUrl const targetPathName = getTargetPathFieldName(input.name); appFields.schema.fileInputs[targetPathName] = Yup.string(); @@ -148,15 +149,14 @@ const FormSchema = (app) => { appFields.schema.fileInputs[targetPathName] = false; appFields.fileInputs[targetPathName] = { label: 'Target Path for ' + input.name, - description: 'The target path is the location to which data are copied from the input. Empty target path or \'*\' indicates, the simple directory or file name from the input path is automatically assign to the target path.', + description: + "The target path is the location to which data are copied from the input. Empty target path or '*' indicates, the simple directory or file name from the input path is automatically assign to the target path.", required: false, readOnly: false, - type: 'text' + type: 'text', }; appFields.defaults.fileInputs[targetPathName] = - input.targetPath === null || typeof input.targetPath === 'undefined' - ? '*' - : input.targetPath; + checkAndSetDefaultTargetPath(input.targetPath); }); return appFields; }; diff --git a/client/src/components/Applications/AppForm/AppFormUtils.js b/client/src/components/Applications/AppForm/AppFormUtils.js index b0469348d..1b5b30c7e 100644 --- a/client/src/components/Applications/AppForm/AppFormUtils.js +++ b/client/src/components/Applications/AppForm/AppFormUtils.js @@ -195,8 +195,29 @@ export const isTargetPathField = (inputFieldName) => { * * @function * @param {String} targetPathFieldName - * @returns {String} actual field name + * @returns {String} actual field name */ export const getInputFieldFromTargetPathField = (targetPathFieldName) => { - return targetPathFieldName.replace(TARGET_PATH_FIELD_PREFIX,''); + return targetPathFieldName.replace(TARGET_PATH_FIELD_PREFIX, ''); +}; + +/** + * Sets the default value if target path is not set. + * + * @function + * @param {String} targetPathFieldValue + * @returns {String} target path value + */ +export const checkAndSetDefaultTargetPath = (targetPathFieldValue) => { + if (targetPathFieldValue === null || targetPathFieldValue === undefined) { + return '*'; + } + + targetPathFieldValue = targetPathFieldValue.trim(); + + if (targetPathFieldValue.trim() === '') { + return '*'; + } + + return targetPathFieldValue; }; From ccc527c9403f125dc47aba37286d5904156bc214 Mon Sep 17 00:00:00 2001 From: Chandra Y Date: Thu, 3 Aug 2023 13:50:58 -0500 Subject: [PATCH 04/16] Rebase and merge --------- Co-authored-by: Sal Tijerina --- .../components/Applications/AppForm/AppForm.jsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/client/src/components/Applications/AppForm/AppForm.jsx b/client/src/components/Applications/AppForm/AppForm.jsx index 650307735..23a0fd2aa 100644 --- a/client/src/components/Applications/AppForm/AppForm.jsx +++ b/client/src/components/Applications/AppForm/AppForm.jsx @@ -466,6 +466,12 @@ export const AppSchemaForm = ({ app }) => { job.fileInputs = Object.values( Object.entries(job.fileInputs) .map(([k, v]) => { + // filter out read only inputs. 'FIXED' inputs are tracked as readOnly + if ( + Object.hasOwn(appFields.fileInputs, k) && + appFields.fileInputs[k].readOnly + ) + return; return { name: k, sourceUrl: !isTargetPathField(k) ? v : null, @@ -498,6 +504,15 @@ export const AppSchemaForm = ({ app }) => { [parameterSet]: Object.entries(parameterValue) .map(([k, v]) => { if (!v) return; + // filter read only parameters. 'FIXED' parameters are tracked as readOnly + if ( + Object.hasOwn( + appFields.parameterSet[parameterSet], + k + ) && + appFields.parameterSet[parameterSet][k].readOnly + ) + return; return parameterSet === 'envVariables' ? { key: k, value: v } : { name: k, arg: v }; From 8775765f4a17d2c4fd7ba20afd680ae69bc91e83 Mon Sep 17 00:00:00 2001 From: Sal Tijerina Date: Thu, 3 Aug 2023 14:04:01 -0500 Subject: [PATCH 05/16] Update changelog --- CHANGELOG.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 718ba7d95..99ee0f596 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.0] - 2023-08-03: v3 model adjustments; bugfixes; core-styles update + +### Added + +- WP-75: Implement cancel job action (#842) + +### Changed + +- WI-29: Setup Django Caching (#828) +- WP-43: System Monitor UI Update (#807) +- Quick: handle 3dem workspace migrations (#833) +- TV3-164: Remove 10 second system credential delay (#840) +- WP-171: Address deprecation warnings in backend (urls and gettext_lazy) (#838) +- WP-208: Do not squash Tapipy exceptions in BaseApiException (#834) +- WP-193: core-stlyes update (#837) + +### Fixed + +- WP-38: Prevent multiple clicks to button, add spinner (#819) +- docs: fix bad desc of branch prefix "style" (#822) +- fix:(ui-patterns): show actual class name passed (#663) +- WP-40: Prevent push keys message and modal for key service systems (#830) +- Quick: Correctly hide "Parameters" string in app form if none exist #832 +- WP-45: Fix Sysmon blank fields for unoperational systems (#841) +- WP-194: Filter out readonly parameters and file inputs from job submission (#843) + +### Removed + +- TV3-139: Cleanup Old Systems Code (#817) + ## [3.0.1] - 2023-06-29: Fix Onboarding Websockets ### Changed @@ -886,7 +916,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [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.0.1...HEAD +[unreleased]: https://github.com/TACC/Core-Portal/compare/v3.1.0...HEAD +[3.1.0]: https://github.com/TACC/Core-Portal/releases/tag/v3.1.0 [3.0.1]: https://github.com/TACC/Core-Portal/releases/tag/v3.0.1 [3.0.0]: https://github.com/TACC/Core-Portal/releases/tag/v3.0.0 [2.24.0]: https://github.com/TACC/Core-Portal/releases/tag/v2.24.0 From d799c089306c7a13f109370a58a82633eb56f943 Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Wed, 16 Aug 2023 09:36:57 -0500 Subject: [PATCH 06/16] bug/WP-230: Prevent other users' files from appearing in search results (#847) * Prevent other users' files from appearing in search results * update tests * linting --------- Co-authored-by: Jake Rosenberg --- server/portal/libs/agave/operations.py | 5 +++++ server/portal/libs/agave/operations_unit_test.py | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/server/portal/libs/agave/operations.py b/server/portal/libs/agave/operations.py index 5377c3578..1603039ee 100644 --- a/server/portal/libs/agave/operations.py +++ b/server/portal/libs/agave/operations.py @@ -110,6 +110,10 @@ def search(client, system, path='', offset=0, limit=100, query_string='', filter List of dicts containing file metadata from Elasticsearch """ + + # Perform a listing to ensure the user has access to the directory they're searching + listing(client, system, path) + if filter == 'Folders': filter_query = Q('term', **{'format': 'folder'}) else: @@ -135,6 +139,7 @@ def search(client, system, path='', offset=0, limit=100, query_string='', filter if filter: search = search.filter(filter_query) + search = search.filter('prefix', **{'path._exact': path}) search = search.filter('term', **{'system._exact': system}) search = search.extra(from_=int(offset), size=int(limit)) res = search.execute() diff --git a/server/portal/libs/agave/operations_unit_test.py b/server/portal/libs/agave/operations_unit_test.py index 031c5c11f..269826025 100644 --- a/server/portal/libs/agave/operations_unit_test.py +++ b/server/portal/libs/agave/operations_unit_test.py @@ -50,8 +50,9 @@ def test_listing(self, mock_indexer): self.assertEqual(ls, {'listing': mock_response_listing, 'reachedEnd': True}) + @patch('portal.libs.agave.operations.listing') @patch('portal.libs.agave.operations.IndexedFile.search') - def test_search(self, mock_search): + def test_search(self, mock_search, mock_listing): mock_hit = Hit({}) mock_hit.system = 'test.system' mock_hit.path = '/path/to/file' @@ -59,7 +60,7 @@ def test_search(self, mock_search): mock_result = MagicMock() mock_result.__iter__.return_value = [mock_hit] mock_result.hits.total.value = 1 - mock_search().query().filter().extra().execute\ + mock_search().query().filter().filter().extra().execute\ .return_value = mock_result search_res = search(None, 'test.system', '/path', query_string='query') @@ -72,8 +73,10 @@ def test_search(self, mock_search): fields=[ "name._exact, name._pattern"], default_operator='and')) - mock_search().query().filter.assert_called_with('term', **{'system._exact': 'test.system'}) - mock_search().query().filter().extra.assert_called_with(from_=int(0), size=int(100)) + + mock_search().query().filter.assert_called_with('prefix', **{'path._exact': '/path'}) + mock_search().query().filter().filter.assert_called_with('term', **{'system._exact': 'test.system'}) + mock_search().query().filter().filter().extra.assert_called_with(from_=int(0), size=int(100)) self.assertEqual(search_res, {'listing': [{'system': 'test.system', 'path': '/path/to/file'}], From b3306428bab3eb8f04cfb39c2fbb24549dc12d80 Mon Sep 17 00:00:00 2001 From: Sal Tijerina Date: Wed, 16 Aug 2023 09:39:58 -0500 Subject: [PATCH 07/16] update Changelog --- CHANGELOG.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99ee0f596..7108d8d94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.1] - 2023-08-16: Fix Files Search Scope + +### Fixed + +- WP-230: Prevent other users' files from appearing in search results (#847) + ## [3.1.0] - 2023-08-03: v3 model adjustments; bugfixes; core-styles update ### Added @@ -29,7 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - docs: fix bad desc of branch prefix "style" (#822) - fix:(ui-patterns): show actual class name passed (#663) - WP-40: Prevent push keys message and modal for key service systems (#830) -- Quick: Correctly hide "Parameters" string in app form if none exist #832 +- Quick: Correctly hide "Parameters" string in app form if none exist (#832) - WP-45: Fix Sysmon blank fields for unoperational systems (#841) - WP-194: Filter out readonly parameters and file inputs from job submission (#843) @@ -916,7 +922,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [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.1.0...HEAD +[unreleased]: https://github.com/TACC/Core-Portal/compare/v3.1.1...HEAD +[3.1.1]: https://github.com/TACC/Core-Portal/releases/tag/v3.1.1 [3.1.0]: https://github.com/TACC/Core-Portal/releases/tag/v3.1.0 [3.0.1]: https://github.com/TACC/Core-Portal/releases/tag/v3.0.1 [3.0.0]: https://github.com/TACC/Core-Portal/releases/tag/v3.0.0 From 74e52d67ad676592ec06cf0f5d1eb302cf8e6f95 Mon Sep 17 00:00:00 2001 From: Jake Rosenberg Date: Tue, 22 Aug 2023 09:41:49 -0500 Subject: [PATCH 08/16] Prevent user search endpoint from returning all user profiles (#848) Co-authored-by: Jake Rosenberg --- server/portal/apps/users/views.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server/portal/apps/users/views.py b/server/portal/apps/users/views.py index 47fadcb73..19f156ebb 100644 --- a/server/portal/apps/users/views.py +++ b/server/portal/apps/users/views.py @@ -107,6 +107,11 @@ def get(self, request): if role: logger.info(role) user_rs = user_rs.filter(groups__name=role) + + # Prevent endpoint from returning unfiltered user list. + if not q and not role: + return HttpResponseNotFound() + resp = [model_to_dict(u, fields=resp_fields) for u in user_rs] if len(resp): return JsonResponse(resp, safe=False) From a3be0f44f1c49b315d1bd91ff14c5e3240d9d95f Mon Sep 17 00:00:00 2001 From: Sal Tijerina Date: Tue, 22 Aug 2023 09:43:44 -0500 Subject: [PATCH 09/16] Update Changelog --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7108d8d94..429f274b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.2] - 2023-08-22: Secure user search endpoint + +### Fixed + +- Prevent user search endpoint from returning all user profiles (#848) + ## [3.1.1] - 2023-08-16: Fix Files Search Scope ### Fixed @@ -922,7 +928,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [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.1.1...HEAD +[unreleased]: https://github.com/TACC/Core-Portal/compare/v3.1.2...HEAD +[3.1.2]: https://github.com/TACC/Core-Portal/releases/tag/v3.1.2 [3.1.1]: https://github.com/TACC/Core-Portal/releases/tag/v3.1.1 [3.1.0]: https://github.com/TACC/Core-Portal/releases/tag/v3.1.0 [3.0.1]: https://github.com/TACC/Core-Portal/releases/tag/v3.0.1 From c56ac0ddc32f8a8c504023f82763a50b1b15da9d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:55:45 -0500 Subject: [PATCH 10/16] build(deps): bump uwsgi from 2.0.21 to 2.0.22 in /server (#853) Bumps [uwsgi](https://github.com/unbit/uwsgi-docs) from 2.0.21 to 2.0.22. - [Commits](https://github.com/unbit/uwsgi-docs/commits) --- updated-dependencies: - dependency-name: uwsgi dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- server/poetry.lock | 8 ++++---- server/pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index 057d4570f..8a6ca196b 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aioredis" @@ -2650,12 +2650,12 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] [[package]] name = "uwsgi" -version = "2.0.21" +version = "2.0.22" description = "The uWSGI server" optional = false python-versions = "*" files = [ - {file = "uwsgi-2.0.21.tar.gz", hash = "sha256:35a30d83791329429bc04fe44183ce4ab512fcf6968070a7bfba42fc5a0552a9"}, + {file = "uwsgi-2.0.22.tar.gz", hash = "sha256:4cc4727258671ac5fa17ab422155e9aaef8a2008ebb86e4404b66deaae965db2"}, ] [[package]] @@ -2781,4 +2781,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "a234d7a6b05dbb19cbae45d1ca2f01436e3ac3806d64ab2471e97468d00e2627" +content-hash = "c40d55ad3a597c47d889feb6d959fa7f75622025eeb7670fffe10dfd01e9b559" diff --git a/server/pyproject.toml b/server/pyproject.toml index 2f816f6a1..798b9a066 100644 --- a/server/pyproject.toml +++ b/server/pyproject.toml @@ -23,7 +23,7 @@ cached-property = "^1.5.1" ipython = "^8.13.2" pycryptodome = "^3.9.7" elasticsearch = "^7.7.1" -uwsgi = "^2.0.18" +uwsgi = "^2.0.22" requests = "^2.31.0" django-impersonate = "^1.5" channels = "^2.4.0" From ab6d9bcaa530b85bfc2acc181987264e58a5948d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:59:44 -0500 Subject: [PATCH 11/16] build(deps): bump certifi from 2022.12.7 to 2023.7.22 in /server (#854) Bumps [certifi](https://github.com/certifi/python-certifi) from 2022.12.7 to 2023.7.22. - [Commits](https://github.com/certifi/python-certifi/compare/2022.12.07...2023.07.22) --- updated-dependencies: - dependency-name: certifi dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- server/poetry.lock | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/server/poetry.lock b/server/poetry.lock index 8a6ca196b..5f746b1be 100644 --- a/server/poetry.lock +++ b/server/poetry.lock @@ -291,13 +291,13 @@ zstd = ["zstandard"] [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, - {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, + {file = "certifi-2023.7.22-py3-none-any.whl", hash = "sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"}, + {file = "certifi-2023.7.22.tar.gz", hash = "sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082"}, ] [[package]] @@ -987,6 +987,7 @@ files = [ {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"}, {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"}, {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"}, + {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d967650d3f56af314b72df7089d96cda1083a7fc2da05b375d2bc48c82ab3f3c"}, {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"}, {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"}, @@ -995,6 +996,7 @@ files = [ {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"}, {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"}, {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"}, + {file = "greenlet-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d4606a527e30548153be1a9f155f4e283d109ffba663a15856089fb55f933e47"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"}, {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"}, @@ -1024,6 +1026,7 @@ files = [ {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"}, {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"}, {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"}, + {file = "greenlet-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1087300cf9700bbf455b1b97e24db18f2f77b55302a68272c56209d5587c12d1"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"}, {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"}, @@ -1032,6 +1035,7 @@ files = [ {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"}, {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"}, {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"}, + {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8512a0c38cfd4e66a858ddd1b17705587900dd760c6003998e9472b77b56d417"}, {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"}, {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"}, From d36ba9ffd6fbdbb99c897aadcec4336dcfd6a27c Mon Sep 17 00:00:00 2001 From: Shayan Khan Date: Thu, 31 Aug 2023 14:41:42 -0500 Subject: [PATCH 12/16] task/WP-189 Handle timeout exit code for interactive app jobs (#851) * added success message and state for interactive job on timeout * linting * created a central utility function, added logic to determine timeout * remove commented code * Added a comment --- server/portal/apps/webhooks/views.py | 4 ++++ server/portal/apps/workspace/api/utils.py | 28 +++++++++++++++++++++++ server/portal/apps/workspace/api/views.py | 8 +++++++ 3 files changed, 40 insertions(+) create mode 100644 server/portal/apps/workspace/api/utils.py diff --git a/server/portal/apps/webhooks/views.py b/server/portal/apps/webhooks/views.py index 702045b27..54544b23d 100644 --- a/server/portal/apps/webhooks/views.py +++ b/server/portal/apps/webhooks/views.py @@ -20,6 +20,7 @@ validate_webhook, execute_callback ) +from portal.apps.workspace.api.utils import check_job_for_timeout from django.conf import settings @@ -54,6 +55,8 @@ def validate_tapis_job(job_uuid, job_owner, disallowed_states=[]): if job_data.status in disallowed_states: return None + job_data = check_job_for_timeout(job_data) + return job_data @@ -122,6 +125,7 @@ def post(self, request, *args, **kwargs): job_details = validate_tapis_job(job_uuid, username, disallowed_states=non_terminal_states) if job_details: event_data[Notification.EXTRA]['remoteOutcome'] = job_details.remoteOutcome + event_data[Notification.EXTRA]['status'] = job_details.status try: logger.info('Indexing job output for job={}'.format(job_uuid)) diff --git a/server/portal/apps/workspace/api/utils.py b/server/portal/apps/workspace/api/utils.py new file mode 100644 index 000000000..9d9923dfa --- /dev/null +++ b/server/portal/apps/workspace/api/utils.py @@ -0,0 +1,28 @@ +import json + + +def get_tapis_timeout_error_messages(job_id): + return [ + 'JOBS_EARLY_TERMINATION Job terminated by Tapis because: TIME_EXPIRED', + f'JOBS_USER_APP_FAILURE The user application ({job_id}) ended with remote status "TIMEOUT" and returned exit code: 0:0.' + ] + + +def check_job_for_timeout(job): + """ + Check an interactive job for timeout status and mark it as finished + since Tapis does not have native support for interactive jobs yet + """ + + if (hasattr(job, 'notes')): + notes = json.loads(job.notes) + + is_failed = job.status == 'FAILED' + is_interactive = notes.get('isInteractive', False) + has_timeout_message = job.lastMessage in get_tapis_timeout_error_messages(job.remoteJobId) + + if is_failed and is_interactive and has_timeout_message: + job.status = 'FINISHED' + job.remoteOutcome = 'FINISHED' + + return job diff --git a/server/portal/apps/workspace/api/views.py b/server/portal/apps/workspace/api/views.py index b79716428..9a94db829 100644 --- a/server/portal/apps/workspace/api/views.py +++ b/server/portal/apps/workspace/api/views.py @@ -23,6 +23,7 @@ from portal.apps.onboarding.steps.system_access_v3 import create_system_credentials from portal.apps.users.utils import get_user_data from .handlers.tapis_handlers import tapis_get_handler +from portal.apps.workspace.api.utils import check_job_for_timeout logger = logging.getLogger(__name__) METRICS = logging.getLogger('metrics.{}'.format(__name__)) @@ -138,6 +139,7 @@ def get(self, request, *args, **kwargs): @method_decorator(login_required, name='dispatch') class JobsView(BaseApiView): + def get(self, request, operation=None): allowed_actions = ['listing', 'search', 'select'] @@ -150,6 +152,12 @@ def get(self, request, operation=None): op = getattr(self, operation) data = op(tapis, request) + if (isinstance(data, list)): + for index, job in enumerate(data): + data[index] = check_job_for_timeout(job) + else: + data = check_job_for_timeout(data) + return JsonResponse( { 'status': 200, From ccccf91e159bbd9388cd756b194cb3f05ec70cec Mon Sep 17 00:00:00 2001 From: Van Go <35277477+van-go@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:28:22 -0500 Subject: [PATCH 13/16] task/wp-105: create common utils function (#850) * task/wp-105: create common utils function * add check is not a string * prettier * run prettier * removed null return, renamed function * prettier --------- Co-authored-by: Sal Tijerina --- .../src/components/_common/Button/Button.jsx | 12 ++---------- client/src/components/_common/CommonUtils.jsx | 19 +++++++++++++++++++ .../components/_common/Sidebar/Sidebar.jsx | 10 ++-------- 3 files changed, 23 insertions(+), 18 deletions(-) create mode 100644 client/src/components/_common/CommonUtils.jsx diff --git a/client/src/components/_common/Button/Button.jsx b/client/src/components/_common/Button/Button.jsx index 1922cc97b..da102f18a 100644 --- a/client/src/components/_common/Button/Button.jsx +++ b/client/src/components/_common/Button/Button.jsx @@ -4,6 +4,7 @@ import Icon from '../Icon'; import styles from './Button.module.css'; import LoadingSpinner from '_common/LoadingSpinner'; +import emptyStringValidator from '_common/CommonUtils'; export const TYPE_MAP = { primary: 'primary', @@ -25,15 +26,6 @@ export const SIZES = [''].concat(Object.keys(SIZE_MAP)); export const ATTRIBUTES = ['button', 'submit', 'reset']; -function isNotEmptyString(props, propName, componentName) { - if (!props[propName] || props[propName].replace(/ /g, '') === '') { - return new Error( - `No text passed to <${componentName}> prop "${propName}". Validation failed.` - ); - } - return null; -} - const Button = ({ children, className, @@ -125,7 +117,7 @@ const Button = ({ ); }; Button.propTypes = { - children: isNotEmptyString, + children: emptyStringValidator, className: PropTypes.string, iconNameBefore: PropTypes.string, iconNameAfter: PropTypes.string, diff --git a/client/src/components/_common/CommonUtils.jsx b/client/src/components/_common/CommonUtils.jsx new file mode 100644 index 000000000..7c91a8096 --- /dev/null +++ b/client/src/components/_common/CommonUtils.jsx @@ -0,0 +1,19 @@ +/** + * Checks that the field is a non-empty string + * @param {Object} props - + * @param {String} propName - name of the property + * @param {String} componentName - name of the component + * @returns {String} Message if error, otherwise null + */ + +export default function emptyStringValidator(props, propName, componentName) { + if ( + !props[propName] || + typeof props[propName] !== 'string' || + props[propName].replace(/ /g, '') === '' + ) { + return new Error( + `No text passed to <${componentName}> prop "${propName}". Validation failed.` + ); + } +} diff --git a/client/src/components/_common/Sidebar/Sidebar.jsx b/client/src/components/_common/Sidebar/Sidebar.jsx index 9568f672a..06e133034 100644 --- a/client/src/components/_common/Sidebar/Sidebar.jsx +++ b/client/src/components/_common/Sidebar/Sidebar.jsx @@ -4,13 +4,7 @@ import { NavLink as RRNavLink } from 'react-router-dom'; import { Nav, NavItem, NavLink } from 'reactstrap'; import Icon from '_common/Icon'; import styles from './Sidebar.module.css'; - -function isNotEmptyString(props, propName, componentName) { - if (!props[propName] || props[propName].replace(/ /g, '') === '') { - return new Error(`No text passed to ${componentName}. Validation failed.`); - } - return null; -} +import emptyStringValidator from '_common/CommonUtils'; const SidebarItem = ({ to, iconName, label, children, disabled, hidden }) => { return ( @@ -38,7 +32,7 @@ const SidebarItem = ({ to, iconName, label, children, disabled, hidden }) => { SidebarItem.propTypes = { to: PropTypes.string.isRequired, iconName: PropTypes.string.isRequired, - label: isNotEmptyString, + label: emptyStringValidator, children: PropTypes.node, disabled: PropTypes.bool, hidden: PropTypes.bool, From 0eeec058704ea542391f16161781c6935019e196 Mon Sep 17 00:00:00 2001 From: Carrie Arnold Date: Mon, 11 Sep 2023 10:11:57 -0500 Subject: [PATCH 14/16] did these changes on a fresh branch, last PR was very stale codebase (#855) --- client/jest.config.js | 2 +- client/jest.setup.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/client/jest.config.js b/client/jest.config.js index 1c7874006..c47b9de4f 100644 --- a/client/jest.config.js +++ b/client/jest.config.js @@ -186,7 +186,7 @@ module.exports = { // unmockedModulePathPatterns: undefined, // Indicates whether each individual test should be reported during the run - // verbose: undefined, + verbose: true, // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode // watchPathIgnorePatterns: [], diff --git a/client/jest.setup.js b/client/jest.setup.js index 119a80e33..e7312d4b5 100644 --- a/client/jest.setup.js +++ b/client/jest.setup.js @@ -1 +1,11 @@ jest.mock('@niivue/niivue', () => {}); + +global.console = { + ...console, + // uncomment to ignore a specific log level + //log: jest.fn(), + //debug: jest.fn(), + //info: jest.fn(), + //warn: jest.fn(), + //error: jest.fn(), +}; From e1b470046089975373d1ec2a2addfed5109af59c Mon Sep 17 00:00:00 2001 From: Chandra Y Date: Tue, 12 Sep 2023 17:48:14 -0500 Subject: [PATCH 15/16] Add target path for every file input Rebase --- .../Applications/AppForm/AppForm.jsx | 55 +++++++++++++++---- .../Applications/AppForm/AppFormSchema.js | 2 +- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/client/src/components/Applications/AppForm/AppForm.jsx b/client/src/components/Applications/AppForm/AppForm.jsx index 23a0fd2aa..df00d755a 100644 --- a/client/src/components/Applications/AppForm/AppForm.jsx +++ b/client/src/components/Applications/AppForm/AppForm.jsx @@ -19,13 +19,17 @@ import { Link } from 'react-router-dom'; import { getSystemName } from 'utils/systems'; import FormSchema from './AppFormSchema'; import { +<<<<<<< HEAD checkAndSetDefaultTargetPath, +======= +>>>>>>> 47de92d3 (Add target path for every file input) isTargetPathField, getInputFieldFromTargetPathField, getQueueMaxMinutes, getMaxMinutesValidation, getNodeCountValidation, getCoresPerNodeValidation, + getTargetPathFieldName, updateValuesForQueue, } from './AppFormUtils'; import DataFilesSelectModal from '../../DataFiles/DataFilesModals/DataFilesSelectModal'; @@ -462,27 +466,35 @@ export const AppSchemaForm = ({ app }) => { onSubmit={(values, { setSubmitting, resetForm }) => { const job = cloneDeep(values); - // Tranform input field values into format that jobs service wants + job.fileInputs = Object.entries(job.fileInputs) + .map(([k, v]) => { + // filter out read only inputs. 'FIXED' inputs are tracked as readOnly + if ( + Object.hasOwn(appFields.fileInputs, k) && + appFields.fileInputs[k].readOnly + ) + return; + return { + name: k, + sourceUrl: v, + targetPath: app.definition.jobAttributes.fileInputs.find( + (file) => file.name === k + ).targetPath, + }; + }) + .filter((fileInput) => fileInput && fileInput.sourceUrl); // filter out any empty values + // Transform input field values into format that jobs service wants job.fileInputs = Object.values( Object.entries(job.fileInputs) .map(([k, v]) => { - // filter out read only inputs. 'FIXED' inputs are tracked as readOnly - if ( - Object.hasOwn(appFields.fileInputs, k) && - appFields.fileInputs[k].readOnly - ) - return; return { name: k, sourceUrl: !isTargetPathField(k) ? v : null, - targetDir: isTargetPathField(k) - ? checkAndSetDefaultTargetPath(v) - : null, + targetDir: isTargetPathField(k) ? v : null, }; }) .filter((fileInput) => fileInput.sourceUrl || fileInput.targetDir) // filter out any empty values - .reduce((acc, entry) => { - // merge input field and targetPath fields into one. + .reduce((acc, entry) => { // merge input field and targetPath fields into one. const key = getInputFieldFromTargetPathField(entry.name); if (!acc[key]) { acc[key] = {}; @@ -585,6 +597,7 @@ export const AppSchemaForm = ({ app }) => { {Object.entries(appFields.fileInputs).map( ([name, field]) => { // TODOv3 handle fileInputArrays https://jira.tacc.utexas.edu/browse/WP-81 +<<<<<<< HEAD return isTargetPathField(name) ? ( { placeholder="Browse Data Files" key={`fileInputs.${name}`} /> +======= + return ( + isTargetPathField(name)? + : + +>>>>>>> 47de92d3 (Add target path for every file input) ); } )} diff --git a/client/src/components/Applications/AppForm/AppFormSchema.js b/client/src/components/Applications/AppForm/AppFormSchema.js index a2e9c53b2..ba89504d2 100644 --- a/client/src/components/Applications/AppForm/AppFormSchema.js +++ b/client/src/components/Applications/AppForm/AppFormSchema.js @@ -135,7 +135,7 @@ const FormSchema = (app) => { input.sourceUrl === null || typeof input.sourceUrl === 'undefined' ? '' : input.sourceUrl; - + // Add targetDir for all sourceUrl const targetPathName = getTargetPathFieldName(input.name); appFields.schema.fileInputs[targetPathName] = Yup.string(); From c0185564ea553c15617f54da0dcff2e734bee2c4 Mon Sep 17 00:00:00 2001 From: Chandra Y Date: Wed, 13 Sep 2023 08:23:58 -0500 Subject: [PATCH 16/16] prettier fix --- client/src/components/Applications/AppForm/AppFormSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Applications/AppForm/AppFormSchema.js b/client/src/components/Applications/AppForm/AppFormSchema.js index ba89504d2..a2e9c53b2 100644 --- a/client/src/components/Applications/AppForm/AppFormSchema.js +++ b/client/src/components/Applications/AppForm/AppFormSchema.js @@ -135,7 +135,7 @@ const FormSchema = (app) => { input.sourceUrl === null || typeof input.sourceUrl === 'undefined' ? '' : input.sourceUrl; - + // Add targetDir for all sourceUrl const targetPathName = getTargetPathFieldName(input.name); appFields.schema.fileInputs[targetPathName] = Yup.string();