From 8c80537ff292c15417ec59d8d8656b4ba2a679c7 Mon Sep 17 00:00:00 2001 From: Birdmachine Date: Wed, 15 Jan 2025 16:57:25 -0500 Subject: [PATCH 1/2] Add Pipeline Testing Privlidge service to Ingest API service and load result into the Dataset Form --- src/src/components/ingest/dataset_edit.jsx | 22 ++++++++++++++++++---- src/src/service/ingest_api.js | 21 +++++++++++++++++++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/src/components/ingest/dataset_edit.jsx b/src/src/components/ingest/dataset_edit.jsx index 74b87abc..acf8709c 100644 --- a/src/src/components/ingest/dataset_edit.jsx +++ b/src/src/components/ingest/dataset_edit.jsx @@ -31,7 +31,8 @@ import { ingest_api_dataset_publish, ingest_api_dataset_submit, ingest_api_notify_slack, - ingest_api_users_groups + ingest_api_users_groups, + ingest_api_pipeline_test_privs } from '../../service/ingest_api'; import {ubkg_api_generate_display_subtype} from "../../service/ubkg_api"; import {getPublishStatusColor} from "../../utils/badgeClasses"; @@ -91,6 +92,7 @@ class DatasetEdit extends Component { has_publish_priv:false, has_version_priv:false, has_manual_priv:false, + has_pipeline_testing_priv:false, groupsToken:"", // Data that sets the scene @@ -143,8 +145,6 @@ class DatasetEdit extends Component { componentDidMount() { var permChecks = [this.state.has_admin_priv,this.state.has_submit_priv,this.state.writeable,this.state.status.toUpperCase(),this.props.newForm] - // var permChecks = [this.state.has_admin_priv,this.state.has_submit_priv,this.state.writeable,this.state.assay_type_primary,this.state.status.toUpperCase(),this.props.newForm] - // console.table({permChecks}); if(this.props.editingDataset && this.props.editingDataset.assigned_to_group_name){ // console.debug('%c⊙ assigned_to_group_name', 'color:#00ff7b', this.props.editingDataset.assigned_to_group_name ); this.setState({assigned_to_group_name:this.props.editingDataset.assigned_to_group_name}) @@ -153,7 +153,21 @@ class DatasetEdit extends Component { // console.debug('%c⊙ ingest_task', 'color:#00ff7b', this.props.editingDataset.ingest_task ); this.setState({ingest_task:this.props.editingDataset.ingest_task}) } - + + // Checking Permissions for Pipeline Testing + ingest_api_pipeline_test_privs(JSON.parse(localStorage.getItem("info")).groups_token) + .then((res) => { + if(res.status >= 200 && res.status < 301){ + this.setState({has_pipeline_testing_priv:res.results.has_pipeline_test_privs}); } + }) + .catch((err) => { + if (err.response && err.response.status === 401) { + this.props.reportError(err); + localStorage.setItem("isAuthenticated", false); + }else if(err.status){ + localStorage.setItem("isAuthenticated", false); + } + }); // @TODO: Better way to listen for off-clicking a modal, seems to trigger rerender of entire page // Modal state as flag for add/remove? diff --git a/src/src/service/ingest_api.js b/src/src/service/ingest_api.js index cfed90b7..791b2254 100644 --- a/src/src/service/ingest_api.js +++ b/src/src/service/ingest_api.js @@ -591,3 +591,24 @@ export function ingest_api_publish_collection(auth, data) { }); }; +/* + * Pipeline Testing Privledges + * + */ +export function ingest_api_pipeline_test_privs(auth) { + const options = {headers:{Authorization: "Bearer " + auth,"Content-Type":"application/json"}}; + let url = `${process.env.REACT_APP_DATAINGEST_API_URL}/has-pipeline-test-privs`; + return axios + .get(url, options) + .then(res => { + console.debug("ingest_api_pipeline_test_privs",res); + let results = res.data; + return {status: res.status, results: results} + }) + .catch(error => { + console.debug('%c⭗ ingest_api_pipeline_test_privs', 'color:#ff005d',error ); + // throw new Error(error); + return {error} + }); +}; + From cb41cb0c2e53da2f0a6ff0d2819dd0f9c4c92daf Mon Sep 17 00:00:00 2001 From: Birdmachine Date: Wed, 22 Jan 2025 11:16:45 -0500 Subject: [PATCH 2/2] Add Submit for Testing Button --- src/src/components/ingest/dataset_edit.jsx | 257 +++++++++++++++------ src/src/service/ingest_api.js | 23 ++ 2 files changed, 204 insertions(+), 76 deletions(-) diff --git a/src/src/components/ingest/dataset_edit.jsx b/src/src/components/ingest/dataset_edit.jsx index acf8709c..68583511 100644 --- a/src/src/components/ingest/dataset_edit.jsx +++ b/src/src/components/ingest/dataset_edit.jsx @@ -1,8 +1,5 @@ import {faPlus,faQuestionCircle,faSpinner,faTrash,faUserShield} from "@fortawesome/free-solid-svg-icons"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -// import Dialog from '@material-ui/core/Dialog'; -// import DialogActions from '@material-ui/core/DialogActions'; -// import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import DialogContent from '@mui/material/DialogContent'; import DialogActions from '@mui/material/DialogActions'; @@ -32,7 +29,8 @@ import { ingest_api_dataset_submit, ingest_api_notify_slack, ingest_api_users_groups, - ingest_api_pipeline_test_privs + ingest_api_pipeline_test_privs, + ingest_api_pipeline_test_submit } from '../../service/ingest_api'; import {ubkg_api_generate_display_subtype} from "../../service/ubkg_api"; import {getPublishStatusColor} from "../../utils/badgeClasses"; @@ -70,6 +68,7 @@ class DatasetEdit extends Component { display_doi:"", editingSource:[], // globus_path: "", + is_primary:false, source_uuid_list:[], source_uuid_type:"", source_uuid:undefined, @@ -101,6 +100,7 @@ class DatasetEdit extends Component { slist:[], // Page States + changed:false, showSubmitModal:false, showRevertModal:false, badge_class:"badge-purple", @@ -129,6 +129,12 @@ class DatasetEdit extends Component { has_other_datatype:false, submitErrorResponse:"", submitErrorStatus:"", + showFeedbackDialog:false, + feedbackDialog:{ + title:"", + message:"", + actions:[] + }, isValidData:true, previousHubIDs:[], nextHubIDs:[], @@ -146,11 +152,9 @@ class DatasetEdit extends Component { componentDidMount() { var permChecks = [this.state.has_admin_priv,this.state.has_submit_priv,this.state.writeable,this.state.status.toUpperCase(),this.props.newForm] if(this.props.editingDataset && this.props.editingDataset.assigned_to_group_name){ - // console.debug('%c⊙ assigned_to_group_name', 'color:#00ff7b', this.props.editingDataset.assigned_to_group_name ); this.setState({assigned_to_group_name:this.props.editingDataset.assigned_to_group_name}) } if(this.props.editingDataset && this.props.editingDataset.ingest_task){ - // console.debug('%c⊙ ingest_task', 'color:#00ff7b', this.props.editingDataset.ingest_task ); this.setState({ingest_task:this.props.editingDataset.ingest_task}) } @@ -165,14 +169,13 @@ class DatasetEdit extends Component { this.props.reportError(err); localStorage.setItem("isAuthenticated", false); }else if(err.status){ - localStorage.setItem("isAuthenticated", false); + this.props.reportError(err); } }); // @TODO: Better way to listen for off-clicking a modal, seems to trigger rerender of entire page // Modal state as flag for add/remove? document.addEventListener("click", this.handleClickOutside); - // this.setAssayLists(); var savedGeneticsStatus = undefined; try { var auth = JSON.parse(localStorage.getItem("info")).groups_token; @@ -195,7 +198,11 @@ class DatasetEdit extends Component { // Figure out our permissions if (this.props.editingDataset) { - // console.debug("DatasetEdit: componentDidMount: editingDataset: " + this.props.editingDataset.uuid); + // Primary check + if(this.props.editingDataset.hasOwnProperty('creation_action') && this.props.editingDataset.creation_action === "Create Dataset Activity"){ + this.setState({is_primary:true}); + } + if(!this.props.editingDataset.previous_revision_uuids){ this.setState({loadingPreviousVersions:false}); } @@ -281,17 +288,14 @@ class DatasetEdit extends Component { if(this.props.editingDataset.direct_ancestors){ console.debug('%c⊙ direct_ancestors', 'color:#5900FF', this.props.editingDataset.direct_ancestors ); // Might have to assemble here for promise reasons? - // ancestorList = this.assembleSourceAncestorData(this.props.editingDataset.direct_ancestors); this.assembleSourceAncestorData(this.props.editingDataset.direct_ancestors) } - // var sourceList = this.assembleSourceAncestorData(this.props.editingDataset.direct_ancestors); this.setState( { status:this.props.editingDataset.hasOwnProperty('status') ? this.props.editingDataset.status.toUpperCase() : "NEW", display_doi:this.props.editingDataset.hubmap_id, lab_dataset_id:this.props.editingDataset.lab_dataset_id, source_uuid:this.getSourceAncestor(this.props.editingDataset.direct_ancestors), - // source_uuid_list:sourceList, source_entity:this.getSourceAncestorEntity(this.props.editingDataset.direct_ancestors), // Seems like it gets the multiples. Multiple are stored here anyways during selection/editing slist:this.getSourceAncestorEntity(this.props.editingDataset.direct_ancestors), contains_human_genetic_sequences:savedGeneticsStatus, @@ -442,6 +446,35 @@ class DatasetEdit extends Component { this.setState({ errorMsgShow:false }); }; + toggleFeedbackDialog = (title,message,actions) => { + console.debug('%c◉ toggleFeedback ', 'color:#00ff7b', title, message, actions); + this.setState({ + showFeedbackDialog:!this.state.showFeedbackDialog, + feedbackDialog:{ + title:title || "", + message:message || "", + actions:actions || [{label:"Close", action:()=>{this.clearFeedbackDialog()}}] + } + },()=>{ + console.debug('%c◉ FeedbackDialog', 'color:#00ff7b', this.state.showFeedbackDialog); + }); + }; + + clearFeedbackDialog = () => { + console.debug('%c◉ clearFeedbackDialog ', 'color:#00ff7b'); + this.setState({ + showFeedbackDialog:false, + feedbackDialog:{ + title:"", + message:"", + actions:[] + } + },()=>{ + console.debug('%c◉ FeedbackDialog Cleared', 'color:#00ff7b'); + }); + }; + + showConfirmDialog(row,index) { this.setState({ confirmDialog:true, @@ -497,59 +530,36 @@ class DatasetEdit extends Component { }; handleInputChange = (e) => { - const {id, name, value} = e.target; - console.debug('%c⊙ handleInputChange', 'color:#00ff7b', id, value ); - switch (name) { - case "lab_dataset_id": - this.setState({lab_dataset_id:value,}); - break; - case "contains_human_genetic_sequences": - let gene_seq = undefined; - if (value === 'yes') { - gene_seq = true; - } else if(value === 'no'){ - gene_seq = false; - } - this.setState({contains_human_genetic_sequences:gene_seq, // need to convert to a boolean - }); - break; - case "description": - this.setState({description:value,}); - break; - case "dataset_info": - this.setState({dataset_info:value,}); - break; - case "status": - this.setState({new_status:value,}); - break; - case "other_dt": - this.setState({ other_dt:value }); - break; - case "newStatus": - this.setState({ newStatus:value }); - break; - case "assigned_to_group_name": - this.setState({ assigned_to_group_name:value }); - break; - case "ingest_task": - this.setState({ ingest_task:value }); - break; - case "dt_select": - this.setState({ - has_other_datatype:false, - dataset_type:value, - dataset_type:value, - }); - break; - case "groups": - this.setState({selected_group:value}); - break; - default: - this.setState({name:value}); - break; + const { name, value } = e.target; + this.setState({ changed: true }); + + const stateUpdateMap = { + "lab_dataset_id": "lab_dataset_id", + "contains_human_genetic_sequences": value === 'yes' ? true : value === 'no' ? false : undefined, + "description": "description", + "dataset_info": "dataset_info", + "status": "new_status", + "other_dt": "other_dt", + "newStatus": "newStatus", + "assigned_to_group_name": "assigned_to_group_name", + "ingest_task": "ingest_task", + "dt_select": { has_other_datatype: false, dataset_type: value }, + "groups": "selected_group" + }; + + const stateKey = stateUpdateMap[name]; + if (stateKey) { + if (typeof stateKey === 'object') { + this.setState(stateKey); + } else { + this.setState({ [stateKey]: value }); + } + } else { + this.setState({ [name]: value }); } }; + handleInputFocus = (e) => { const { name, value } = e.target; switch (name) { @@ -1206,6 +1216,41 @@ class DatasetEdit extends Component { }); } + handleSaveCheck = () => { + if(this.state.changed){ + this.toggleFeedbackDialog( + "Continue Without Saving", + "You have unsaved changes. Are you sure you want to continue without saving?", + [ + {label:"Continue", action:()=>{this.handleSubmitForTesting()}}, + {label:"Save", action:()=>{this.handleSubmitForTesting()}}, + {label:"Close", action:()=>{this.clearFeedbackDialog("","","")}}, + ]); + }else{ + this.handleSubmitForTesting() + } + // Check if the form data's been updated and warn user about loss without saving + + + } + + handleSubmitForTesting = () => { + // this.handleContinueWithoutSave(); + console.debug('%c◉ Submitting for Testing ', 'color:#00ff7b', ); + ingest_api_pipeline_test_submit(JSON.parse(localStorage.getItem("info")).groups_token, {"uuid":this.props.editingDataset.uuid}) + .then((response) => { + console.debug('%c◉ SUBMITTED', 'color:#00ff7b', response); + this.toggleFeedbackDialog( + "Testing Submitted", + response.results, + [{label:"Close", action:()=>{this.clearFeedbackDialog()}}]); + }) + .catch((error) => { + this.setState({submit_error:true, + submitting:false,}); + }) + } + validateForm() { console.debug("validateForm"); return new Promise((resolve, reject) => { @@ -1400,6 +1445,26 @@ class DatasetEdit extends Component { ) } + renderDialogActionButtons(actions){ + console.debug('%c◉ renderDialogActionButtons ', 'color:#00ff7b', actions); + if(actions && actions.length > 1){ // remember: we have the close button by default + actions.map((action, index) => { + console.debug('%c◉ innermap ACTION ', 'color:#00ff7b', action, actions[index].action); + return ( + + ) + + }) + + } + } + renderManualStatusControl=()=>{ return( @@ -1462,23 +1527,25 @@ class DatasetEdit extends Component { // && this.state.status.toUpperCase() === "PUBLISHED"); // console.table([this.state.has_admin_priv, this.state.assay_type_primary, this.state.previous_revision_uuid, this.state.status]); + + // @TODO: Handling in a utility will optimize this a bunch - var writeCheck = this.state.has_write_priv - var adminCheck = this.state.has_admin_priv - var manualCheck = this.state.has_manual_priv - var versCheck = this.state.has_version_priv - var pubCheck = this.state.status === "Published" - var newFormCheck = this.props.newForm - var newStateCheck = this.state.status === "New" + // var writeCheck = this.state.has_write_priv + // var adminCheck = this.state.has_admin_priv + // var manualCheck = this.state.has_manual_priv + // var versCheck = this.state.has_version_priv + // var newFormCheck = this.props.newForm + // var pubCheck = this.state.status === "Published" + // var newStateCheck = this.state.status === "New" var permMatrix = { - "writeCheck":writeCheck, - "adminCheck":adminCheck, - "versCheck":versCheck, - "pubCheck":pubCheck, - "newFormCheck":newFormCheck, - "newStateCheck":newStateCheck, - "manualCheck":manualCheck, + "writeCheck":this.state.has_write_priv, + "adminCheck":this.state.has_admin_priv, + "manualCheck":this.state.has_manual_priv, + "versCheck":this.state.has_version_priv, + "newFormCheck":this.props.newForm, + "pubCheck":this.state.status === "Published", + "newStateCheck":this.state.status=== "New", } // console.debug("permMatrix") // console.table(permMatrix) @@ -1520,6 +1587,18 @@ class DatasetEdit extends Component { )} + { (this.state.has_pipeline_testing_priv || this.state.has_admin_priv) && this.state.is_primary === true &&( +
+ +
+ )} {this.cancelButton()} ) @@ -1577,6 +1656,31 @@ class DatasetEdit extends Component { ); } + + renderFeedbackModall = () => { + return ( + + +

{this.state.feedbackDialog['title']}

+
{this.state.feedbackDialog['message']}
+
+ + + {/* {() => this.renderDialogActionButtons(this.state.feedbackDialog['actions'])} */} + + + +
+ + ); + } renderListItem(uuid){ console.debug('%c◉ data ', 'color:#00ff7b', uuid); @@ -2232,6 +2336,7 @@ name, display_doi, doi {this.renderSubmitModal()} + {this.renderFeedbackModall()} ); diff --git a/src/src/service/ingest_api.js b/src/src/service/ingest_api.js index 791b2254..13363d40 100644 --- a/src/src/service/ingest_api.js +++ b/src/src/service/ingest_api.js @@ -612,3 +612,26 @@ export function ingest_api_pipeline_test_privs(auth) { }); }; + +/* + * Pipeline Testing Submit + * + */ +export function ingest_api_pipeline_test_submit(auth, data) { + const options = {headers:{Authorization: "Bearer " + auth,"Content-Type":"application/json"}}; + let url = `${process.env.REACT_APP_DATAINGEST_API_URL}/datasets/${data['uuid']}/submit-for-pipeline-testing`; + console.debug('%c◉ url ', 'color:#00ff7b', url); + return axios + .post(url, {}, options) + .then(res => { + console.debug("ingest_api_pipeline_test_submit",res); + let results = res.data; + return {status: res.status, results: results} + }) + .catch(error => { + console.debug('%c⭗ ingest_api_pipeline_test_submit', 'color:#ff005d',error ); + // throw new Error(error); + return {error} + }); +}; +