From 7bffdb5d806467d2863d0bef2175143f2fd17b91 Mon Sep 17 00:00:00 2001 From: "Bailey K. Srimoungchanh" Date: Fri, 22 May 2026 12:55:43 -0500 Subject: [PATCH] feat(qaqc): add QA/QC button --- src/modules/page/page.constants.js | 1 + src/modules/project/datasets/DatasetDetail.js | 52 +++++++++++- src/modules/qaqc/qaqc_funcs.js | 85 +++++++++++++++++++ src/services/network/urls.constants.js | 4 + 4 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 src/modules/qaqc/qaqc_funcs.js diff --git a/src/modules/page/page.constants.js b/src/modules/page/page.constants.js index 5161af655..710b3818c 100644 --- a/src/modules/page/page.constants.js +++ b/src/modules/page/page.constants.js @@ -194,6 +194,7 @@ export const SECONDARY_PAGES = [ icon_pressed_src: require('../../assets/icons/QAQC_pressed.png'), overview_component: QAQCOverview, page_component: QAQCPage, + testing: true, }, ]; diff --git a/src/modules/project/datasets/DatasetDetail.js b/src/modules/project/datasets/DatasetDetail.js index be6ad1a4a..f7800bff7 100644 --- a/src/modules/project/datasets/DatasetDetail.js +++ b/src/modules/project/datasets/DatasetDetail.js @@ -20,6 +20,11 @@ import SidePanelHeader from '../../main-menu-panel/sidePanel/SidePanelHeader'; import {setReadOnlyDatasetsIds, updatedDatasetProperties} from '../projects.slice'; import useProject from '../useProject'; +//PADLOCK +import ActionButton from '../../../shared/ui/buttons/ActionButton'; +import ModalWrapper from '../../../shared/ui/modals/ModalWrapper'; +import {runQAQC} from '../../qaqc/qaqc_funcs'; + const DatasetDetail = ({closeDetailView, dataset}) => { /* Data Hooks */ @@ -28,7 +33,7 @@ const DatasetDetail = ({closeDetailView, dataset}) => { const readOnlyDatasetsIds = useSelector(state => state.project.readOnlyDatasetsIds) || []; const targetDatasetId = useSelector(state => state.project.targetDatasetId); - const {initializeDownloadImages} = useDownload(); + const {initializeDownloadImages, initializeDownload} = useDownload(); const {destroyDataset} = useProject(); const [neededImagesCount, refreshNeededImagesCount] = useDatasetNeededImagesCount(dataset); const toast = useToast(); @@ -55,6 +60,13 @@ const DatasetDetail = ({closeDetailView, dataset}) => { const handleDeletePressed = () => setIsDeleteConfirmModalVisible(true); + //PADLOCK + const [isQAQCModalVisible, setIsQAQCModalVisible] = useState(false); + const handleQAQCPressed = () => setIsQAQCModalVisible(true); + const encodedLogin = useSelector(state => state.user.encoded_login); + const {project} = useSelector(state => state.project); + const isTestingMode = useSelector(state => state.project.isTestingMode); + const onToggleReadOnly = () => dispatch(setReadOnlyDatasetsIds(dataset.id)); /* Logic Helpers */ @@ -299,6 +311,40 @@ const DatasetDetail = ({closeDetailView, dataset}) => { ); }; + //PADLOCK + // QAQC Dataset Button + const renderQAQCDatasetButton = () => { + if(isTestingMode){ + return ( + + + + ); + } else return; + }; + + const renderQAQCModal = () => { + return ( + { + await runQAQC(dataset, project.id, setIsQAQCModalVisible, encodedLogin, toast); + initializeDownload(project, encodedLogin); + }} + onCancelPress={() => setIsQAQCModalVisible(false)} + overlayStyleOverride={{height: 'auto'}} + > + {"Confirm the target dataset:\n"+ datasetName} + + ); + }; + /* View */ return ( @@ -314,11 +360,13 @@ const DatasetDetail = ({closeDetailView, dataset}) => { {renderSpotsField()} {renderImagesField()} + {renderQAQCDatasetButton()} {renderReadOnlyDatasetButton()} {Platform.OS === 'web' && renderDeleteDatasetButton()} - {/* Child Modal */} + {/* Child Modal */} + {isQAQCModalVisible && renderQAQCModal()} {isDeleteConfirmModalVisible && renderDeleteConfirmationModal()} ); diff --git a/src/modules/qaqc/qaqc_funcs.js b/src/modules/qaqc/qaqc_funcs.js new file mode 100644 index 000000000..dcc110068 --- /dev/null +++ b/src/modules/qaqc/qaqc_funcs.js @@ -0,0 +1,85 @@ +import {QAQC_PATHS} from "../../services/network/urls.constants"; +import {getRequest} from "../../services/network/serverRequestHelpers"; + +const basicAuth = (token = encoded_login) => ({type: 'basic', token}); + +export const startQAQC = async (dataset, projectID, encodedLogin, toast) => { + console.log("startQAQC()"); + if (!dataset.id) { + console.error('Dataset ID is missing'); + alert('Error: Project ID is missing'); + return; + } + + if (!encodedLogin) { + console.error('User credentials are missing'); + alert('Error: User credentials are missing'); + return; + } + + + toast_id = toast.show( + `Started QAQC process for ${dataset.name}, please wait until download...`, + { + type: 'normal', + animationType: 'slide-in', + duration: 20000, + placement: 'top', + }); + + try{ + // Python script parameters + const params = new URLSearchParams({ + 'action': 'completeness', + 'plainreq': 'true', + 'doAnnotate': 'true', + 'pid': projectID, + 'dsid': dataset.id, + }); + + // Send the request + const response = await getRequest(`${QAQC_PATHS.URL}/api.php?${params.toString()}`, basicAuth(encodedLogin), {responseType: 'document'}); + + // Block until response + await response.text(); + console.log("response.text() passed") + + if (!response.ok) { + toast.hide(toast_id); + throw new Error(`HTTP error. status: ${response.status}`); + } + + // I think we want to force the user to download QAQC notes to avoid desync with local datasets + // This just stalls for 3 seconds, the initializeDownload call is in DatasetDetails + for (let iter = 3; iter > 0; iter--){ + toast.update(toast_id, + `Downloading ${dataset.name} in ${iter} seconds.`, + {type: 'warning'} + ); + await new Promise(r => setTimeout(r, 1000)); + }; + + toast.hide(toast_id); + + return response; + + } catch(err){ + console.error(`startQAQC(): ${err}`) + throw(err) + } + +}; + +export const runQAQC = async (dataset, currentProjectId, setModalVisible, encodedLogin, toast) => { + console.log("runQAQC()") + setModalVisible(false); + if (dataset && dataset.id) { + try { + return await startQAQC(dataset, currentProjectId, encodedLogin, toast); + } catch (err) { + console.error(`runQAQC(): ${err}`); + alert(`runQAQC Error: ${err.message}`); + } + } + else console.error('Target dataset or id is undefined!'); + }; \ No newline at end of file diff --git a/src/services/network/urls.constants.js b/src/services/network/urls.constants.js index a57738a80..ff39bced3 100644 --- a/src/services/network/urls.constants.js +++ b/src/services/network/urls.constants.js @@ -80,3 +80,7 @@ export const SUPPORT_PATHS = { GITHUB: 'https://github.com/StraboSpot/StraboField/issues/new/choose', EMAIL: 'mailto: strabospot@gmail.com?subject=StraboSpot2%20Issue', }; + +export const QAQC_PATHS = { + URL: 'https://strabo.analysis.cool', +}; \ No newline at end of file