From a73724b1e359caa023b6c4f4e2dc8490c84858f4 Mon Sep 17 00:00:00 2001 From: Isuru Gunawardana Date: Tue, 25 Jul 2023 20:55:37 -0700 Subject: [PATCH] archived and non archived flows when uploading an existing file --- .../documents/mine_document_search_util.py | 17 +- .../api/services/document_manager_service.py | 10 +- .../minespace-web/common/constants/API.js | 2 +- .../ProjectSummaryFileUpload.js | 235 ++++++------------ .../src/components/common/FileUpload.js | 99 ++++---- .../src/styles/components/FileReplace.scss | 23 ++ services/minespace-web/src/styles/index.scss | 1 + 7 files changed, 164 insertions(+), 223 deletions(-) create mode 100644 services/minespace-web/src/styles/components/FileReplace.scss diff --git a/services/core-api/app/api/mines/documents/mine_document_search_util.py b/services/core-api/app/api/mines/documents/mine_document_search_util.py index b69028ef16..da367fe35f 100644 --- a/services/core-api/app/api/mines/documents/mine_document_search_util.py +++ b/services/core-api/app/api/mines/documents/mine_document_search_util.py @@ -80,20 +80,17 @@ def find_by_document_name_and_project_guid(cls, document_name, project_guid=None psdx_alias = aliased(ProjectSummaryDocumentXref) ps_alias = aliased(ProjectSummary) p_alias = aliased(Project) - - # Define the ON clause explicitly - on_clause = md_alias.mine_document_guid == psdx_alias.mine_document_guid - + query = query\ .select_from(md_alias)\ - .join(psdx_alias, on_clause)\ + .with_entities(md_alias.document_name, md_alias.mine_document_guid, p_alias.project_guid, md_alias.is_archived, \ + md_alias.mine_guid, md_alias.document_class, md_alias.update_timestamp, md_alias.update_user)\ .filter(md_alias.document_name == document_name, md_alias.deleted_ind == False)\ - .join(ps_alias, ps_alias.project_summary_id == psdx_alias.project_summary_id)\ + .join(psdx_alias, psdx_alias.mine_document_guid == md_alias.mine_document_guid)\ + .join(ps_alias, psdx_alias.project_summary_id == ps_alias.project_summary_id)\ .join(p_alias, p_alias.project_guid == ps_alias.project_guid)\ .filter(p_alias.project_guid == project_guid) - - resp = query.first() - - return resp + + return query.first() raise ValueError("Missing 'project_guid', This is required to continue the file upload process.") \ No newline at end of file diff --git a/services/core-api/app/api/services/document_manager_service.py b/services/core-api/app/api/services/document_manager_service.py index ef51b223ec..8e5ebfae4d 100644 --- a/services/core-api/app/api/services/document_manager_service.py +++ b/services/core-api/app/api/services/document_manager_service.py @@ -31,10 +31,16 @@ def validateFileNameAndInitializeFileUploadWithDocumentManager(cls, request, min if not mine_document: # No existing file found in this application hence continuing the file uploading return DocumentManagerService.initializeFileUploadWithDocumentManager(request, mine, document_category) elif mine_document.is_archived: # An archived file with the same name in this application found, hence responing with 409 - content = f'{{"description" : "File already exist with the given name: {file_name}. Replace with the new version", "status_code": 409, "status": "ARCHIVED_FILE_EXIST"}}' + content = f'{{"description" : "Archived file already exist with the given name: {file_name}", "status_code": 409, \ + "status": "ARCHIVED_FILE_EXIST", "file_name": "{file_name}", "mine_guid": "{mine_document.mine_guid}", \ + "file_type": "{mine_document.document_class}", "update_timestamp": "{mine_document.update_timestamp}", "update_user": "{mine_document.update_user}", \ + "mine_document_guid": "{mine_document.mine_document_guid}"}}' return Response(content, 409) else: # The found file with the same name in this application is not archived. - content = f'{{"description" : "Archived file already exist with the given name: {file_name}", "status_code": 409, "status": "REPLACEABLE_FILE_EXIST"}}' + content = f'{{"description" : "File already exist with the given name: {file_name}. Replace with the new version", \ + "status_code": 409, "status": "REPLACEABLE_FILE_EXIST", "file_name": "{file_name}", "mine_guid": "{mine_document.mine_guid}", \ + "file_type": "{mine_document.document_class}", "update_timestamp": "{mine_document.update_timestamp}", "update_user": "{mine_document.update_user}", \ + "mine_document_guid": "{mine_document.mine_document_guid}"}}' return Response(content, 409, content_type='application/json') @classmethod diff --git a/services/minespace-web/common/constants/API.js b/services/minespace-web/common/constants/API.js index 705eb643d1..782785cbb2 100644 --- a/services/minespace-web/common/constants/API.js +++ b/services/minespace-web/common/constants/API.js @@ -138,7 +138,7 @@ export const PROJECT_SUMMARY_DOCUMENTS = ({ projectGuid, projectSummaryGuid, min )}`; //New version upload export const NEW_VERSION_PROJECT_SUMMARY_DOCUMENTS = ({ mineGuid, mineDocumentGuid }) => - `mines/${mineGuid}/documents/${mineDocumentGuid}/versions/upload`; + `/mines/${mineGuid}/documents/${mineDocumentGuid}/versions/upload`; export const PROJECT_SUMMARY_DOCUMENT = ( projectGuid, projectSummaryGuid, diff --git a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryFileUpload.js b/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryFileUpload.js index 76c17c0ce1..ebadb152c1 100644 --- a/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryFileUpload.js +++ b/services/minespace-web/src/components/Forms/projects/projectSummary/ProjectSummaryFileUpload.js @@ -7,12 +7,14 @@ import { } from "@common/constants/API"; import FileUpload from "@/components/common/FileUpload"; import { Alert, Form, Typography, Modal, Table, Divider, Popconfirm } from "antd"; +import { fetchUserMineInfo } from "@/actionCreators/userDashboardActionCreator"; const propTypes = { onFileLoad: PropTypes.func.isRequired, onRemoveFile: PropTypes.func.isRequired, acceptedFileTypesMap: PropTypes.objectOf(PropTypes.string).isRequired, params: PropTypes.objectOf(PropTypes.string).isRequired, + fetchUserMineInfo: PropTypes.func.isRequired, }; const notificationDisabledStatusCodes = [409]; // Define the notification disabled status codes @@ -22,67 +24,36 @@ export const ProjectSummaryFileUpload = (props) => { const [isArchivedFileModalVisible, setIsArchivedFileModalVisible] = useState(false); const [relaplaceableFileModalMessage, setRelaplaceableFileModalMessage] = useState(""); const [isRelaplaceableFileModalVisible, setRelaplaceableFileModalVisible] = useState(false); - const [isFileUploading, setIsFileUploading] = useState(false); - const [uploading, setUploading] = useState(false); const [fileName, setFileName] = useState(""); - const [uploadUrl, setUploadUrl] = useState(""); - const [uploadKey, setUploadKey] = useState(0); + const [shouldReplaceFile, setShouldReplaceFile] = useState(false); + const [replaceFileUploadUrl, setReplaceFileUploadUrl] = useState(false); + const [mineDocumentGuid, setMineDocumentGuid] = useState(null); + const [mineGuid, setMineGuid] = useState(null); + const [fileDetails, setFileDetails] = useState(null); const handleCloseModal = () => { - console.log("Closing...."); setIsArchivedFileModalVisible(false); setRelaplaceableFileModalVisible(false); }; const childRef = useRef(null); - const handleManualUpload = () => { - // Call the upload.start() function using the ref - const uploadInstance = childRef.current?.props?.onFileLoad(); - // Do whatever you need with the 'uploadInstance', you can start the upload by calling 'uploadInstance.start()' - }; - const handleNewVersionSubmit = () => { - console.log("TODO: handleNewVersionSubmit()"); setRelaplaceableFileModalVisible(false); - console.log("TODO: callFileUpload()"); - // setIsFileUploading(true) - setUploadUrl(NEW_VERSION_PROJECT_SUMMARY_DOCUMENTS('mine_guid', 'mine_document_guid')); - // setUploadRetryCount(uploadRetryCount + 1); - handleManualUpload(); - - // childRef.current.startFileUpload(); - - // - // props.onFileLoad(fileName, document_manager_guid) - // props.onFileLoad(fileName, "document_manager_guid") - - console.log("-->>>>props.onFileReplace"); + setReplaceFileUploadUrl( + NEW_VERSION_PROJECT_SUMMARY_DOCUMENTS({ + mineGuid: props.params.mineGuid, + mineDocumentGuid: mineDocumentGuid, + }) + ); + setShouldReplaceFile(true); }; - const dataOriginal = [ - { - fileName: "file1.pdf", - fileType: "spatial", - date: "Nov 23 2022", - uploader: "idr/ignw", - }, - ]; - - const dataNew = [ - { - fileName: "file1.pdf", - fileType: "spatial", - date: "Nov 23 2022", - uploader: "idr/ignw", - }, - ]; - const columns = [ - { dataIndex: "fileName", width: "40%" }, - { dataIndex: "fileType", width: "20%" }, - { dataIndex: "date", width: "20%" }, - { dataIndex: "uploader", width: "20%" }, + { dataIndex: "fileName" }, + { dataIndex: "fileType" }, + { dataIndex: "date" }, + { dataIndex: "uploader" }, ]; return ( @@ -92,34 +63,43 @@ export const ProjectSummaryFileUpload = (props) => { id="fileUpload" name="fileUpload" component={FileUpload} - addFileStart={() => setUploading(isFileUploading)} + shouldReplaceFile={shouldReplaceFile} uploadUrl={PROJECT_SUMMARY_DOCUMENTS(props.params)} + replaceFileUploadUrl={replaceFileUploadUrl} acceptedFileTypesMap={props.acceptedFileTypesMap} onFileLoad={props.onFileLoad} onRemoveFile={props.onRemoveFile} notificationDisabledStatusCodes={notificationDisabledStatusCodes} allowRevert allowMultiple - key={uploadKey} onError={(filename, e) => { setFileName(fileName); if ( e.response.status_code && notificationDisabledStatusCodes.includes(e.response.status_code) ) { - console.log("Error STATUS: ", e.response.status); if (e.response.status === "ARCHIVED_FILE_EXIST") { let message = `An archived file named ${filename} already exists. If you would like to restore it, download the archived file and upload it again with a different file name.`; setArchivedFileModalMessage(message); setIsArchivedFileModalVisible(true); } if (e.response.status === "REPLACEABLE_FILE_EXIST") { - //replace belows let message = `A file with the same name already exists in this project. Replacing it will create a new version of the original file and replace it as part of this submission.`; setRelaplaceableFileModalMessage(message); setRelaplaceableFileModalVisible(true); - console.log("_______________RESP", e.response); - console.log("_______________ORGREQ", e.originalRequest); + setMineGuid(e.response.mine_guid); + setMineDocumentGuid(e.response.mine_document_guid); + + const date = new Date(e.response.update_timestamp); + const options = { year: "numeric", month: "short", day: "2-digit" }; + const formattedDate = date.toLocaleDateString("en-US", options); + + setFileDetails({ + file_name: filename, + file_type: e.response.file_type, + update_timestamp: `${formattedDate}`, + update_user: e.response.update_user, + }); } } }} @@ -149,31 +129,13 @@ export const ProjectSummaryFileUpload = (props) => { okText="Yes, replace" cancelText="Cancel" width={1000} - // orientationMargin="0" - // bodyStyle={{ padding: '24px 0px 24px 0px'}} - className="custom-modal" + className="new-file-replace-modal" > - Replace File?
- {/* // onFinish={() => props.handleSubmit(props.documents).then(props.closeModal)} */} - { description={relaplaceableFileModalMessage} /> - - <> - - - Original Document - - - - Upload new file - -
- + + Original Document + +
+ + Upload new file + +
@@ -221,64 +191,3 @@ export const ProjectSummaryFileUpload = (props) => { ProjectSummaryFileUpload.propTypes = propTypes; export default ProjectSummaryFileUpload; - -// import React, { useRef } from 'react'; -// import ChildComponent from './ChildComponent'; - -// const ParentComponent = () => { -// const childRef = useRef(); - -// const handleClick = () => { -// childRef.current.myFunction(); -// }; - -// return ( -//
-//

Parent Component

-// -// -//
-// ); -// }; - -// export default ParentComponent; - -// const handleNewVersionSubmit = () => { -// console.log('TODO: handleNewVersionSubmit()') -// setRelaplaceableFileModalVisible(false); -// console.log('TODO: callFileUpload()') - -// if (childRef.current) { -// console.log('CALLLLING child function________________________') -// childRef.current.childFunction(); // Step 2: Call the child function using the ref -// } - -// childRef.current.childFunction(); - -// const fileUpload = new FileUpload(props); -// // fileUpload.setFileUploading(true); -// fileUpload.addFileStart = true - -// // fileUpload.uploadUrl = 'http://localhost:3000/upload'; -// // fileUpload.onFileLoad = onFileLoad -// // // fileUpload.server.process.up - -// // return ( -// // { -// // console.log('Error in uploading ', filename, ' as new version ', e) -// // } -// // } -// // /> -// // ) -// } diff --git a/services/minespace-web/src/components/common/FileUpload.js b/services/minespace-web/src/components/common/FileUpload.js index bcb98b7caf..d9710ba225 100644 --- a/services/minespace-web/src/components/common/FileUpload.js +++ b/services/minespace-web/src/components/common/FileUpload.js @@ -40,7 +40,9 @@ const propTypes = { beforeDropFile: PropTypes.func, itemInsertLocation: PropTypes.func | PropTypes.string, notificationDisabledStatusCodes: PropTypes.arrayOf(PropTypes.number), - onFileReplace: PropTypes.func, + shouldReplaceFile: PropTypes.bool, + replaceFileUploadUrl: PropTypes.string, + file: PropTypes.object, }; const defaultProps = { @@ -62,67 +64,76 @@ const defaultProps = { beforeDropFile: () => {}, itemInsertLocation: "before", notificationDisabledStatusCodes: [], - onFileReplace: () => {}, + shouldReplaceFile: false, + replaceFileUploadUrl: "", + file: null, }; class FileUpload extends React.Component { constructor(props) { - console.log(">>>>>>>>>>>>>>>>>CONSTRUCTOR"); super(props); this.pondRef = React.createRef(); + this.state = { + file: null, + }; + this.server = { process: (fieldName, file, metadata, load, error, progress, abort) => { - console.log(">>>>>>>>>>>>>>>>>CONSTRUCTOR 73"); - const upload = new tus.Upload(file, { + if (file) { + this.setState({ file: file, progress: progress, load: load }); + } + let fileToUpload = this.state.file ? this.state.file : file; + let progressFn = this.state.progress ? this.state.progress : progress; + let loadFn = this.state.load ? this.state.load : load; + const upload = new tus.Upload(fileToUpload, { endpoint: ENVIRONMENT.apiUrl + this.props.uploadUrl, retryDelays: [100, 1000, 3000], removeFingerprintOnSuccess: true, chunkSize: this.props.chunkSize, metadata: { - filename: file.name, - filetype: file.type || APPLICATION_OCTET_STREAM, + filename: fileToUpload.name, + filetype: fileToUpload.type || APPLICATION_OCTET_STREAM, }, headers: createRequestHeader().headers, onError: (err) => { - console.log(">>>>>>>>>>>>>>>>>CONSTRUCTOR 86", err.originalRequest.response); - err.response = JSON.parse(err.originalRequest.response); - err.originalRequest = err.originalRequest; + try { + err.response = JSON.parse(err.originalRequest.response); + err.originalRequest = err.originalRequest; - console.log("FILE_UPLOAD - err.response.status_code : ", err.response.status_code); - console.log( - "FILE_UPLOAD - this.props.notificationDisabledStatusCodes : ", - this.props.notificationDisabledStatusCodes - ); - - if ( - !( - this.props.notificationDisabledStatusCodes.length && - this.props.notificationDisabledStatusCodes.includes(err.response.status_code) - ) - ) { - console.log("Notifications enbaled...."); + if ( + !( + this.props.notificationDisabledStatusCodes.length && + this.props.notificationDisabledStatusCodes.includes(err.response.status_code) + ) + ) { + notification.error({ + message: `Failed to upload ${ + file && fileToUpload.name ? fileToUpload.name : "" + }: ${err}`, + duration: 10, + }); + } else { + console.log( + "notification disabled for status code(s):", + this.props.notificationDisabledStatusCodes + ); + } + this.props.onError(file && fileToUpload.name ? fileToUpload.name : "", err); + } catch (err) { notification.error({ - message: `Failed to upload ${file.name}: ${err}`, + message: `Failed to upload the file: ${err}`, duration: 10, }); - } else { - // remove else - console.log("notification disabled\n", this.props.notificationDisabledStatusCodes); } - - this.props.onError(file.name, err); - error(err); }, onProgress: (bytesUploaded, bytesTotal) => { - console.log(">>>>>>>>>>>>>>>>>CONSTRUCTOR 108"); - progress(true, bytesUploaded, bytesTotal); + progressFn(true, bytesUploaded, bytesTotal); }, onSuccess: async () => { - console.log(">>>>>>>>> >>>>>>>>CONSTRUCTOR 112"); const documentGuid = upload.url.split("/").pop(); - load(documentGuid); - this.props.onFileLoad(file.name, documentGuid); + loadFn(documentGuid); + this.props.onFileLoad(fileToUpload.name, documentGuid); // Call an additional action on file blob after success(only one use case so far, may need to be extended/structured better in the future) if (this.props?.afterSuccess?.action) { try { @@ -148,10 +159,8 @@ class FileUpload extends React.Component { }, }); upload.start(); - console.log(">>>>>>>>>>>>>>>>>CONSTRUCTOR 141"); return { abort: () => { - console.log(">>>>>>>>>>>>>>>>>CONSTRUCTOR 144"); upload.abort(); abort(); }, @@ -160,15 +169,12 @@ class FileUpload extends React.Component { }; } - childFunction = () => { - console.log("C-___________________________________hild function is called!"); - }; - - startFileUpload = () => { - console.log(".............STARTFILEUPLOAD"); - // const inputRef = this.inputRef; - // inputRef.current.click(); // This will trigger the file input click event to open the file dialog - }; + componentDidUpdate(prevProps) { + if (prevProps.shouldReplaceFile !== this.props.shouldReplaceFile) { + this.props.uploadUrl = this.props.replaceFileUploadUrl; + this.server.process(); + } + } render() { const fileValidateTypeLabelExpectedTypesMap = invert(this.props.acceptedFileTypesMap); @@ -197,7 +203,6 @@ class FileUpload extends React.Component { itemInsertLocation={this.props?.itemInsertLocation} credits={null} fileValidateTypeLabelExpectedTypesMap={fileValidateTypeLabelExpectedTypesMap} - onFileReplace={this.server.process.start} fileValidateTypeDetectType={(source, type) => new Promise((resolve, reject) => { // If the browser can't automatically detect the file's MIME type, use the one stored in the "accepted file types" map. diff --git a/services/minespace-web/src/styles/components/FileReplace.scss b/services/minespace-web/src/styles/components/FileReplace.scss new file mode 100644 index 0000000000..150a4a8c10 --- /dev/null +++ b/services/minespace-web/src/styles/components/FileReplace.scss @@ -0,0 +1,23 @@ +.new-file-replace-table { + .ant-table-tbody > tr.ant-table-row > td { + border-bottom: none; + } +} + +.new-file-replace-modal { + .ant-modal-body { + padding-bottom: 24px; + padding-left: 0px; + padding-right: 0px; + padding-top: 24px; + } + .ant-modal-footer { + padding: 24px; + } + .ant-table-cell { + width: 20%; + } + .ant-table-row td:first-child { + width: 40%; + } +} diff --git a/services/minespace-web/src/styles/index.scss b/services/minespace-web/src/styles/index.scss index 831470a025..6e1b379343 100755 --- a/services/minespace-web/src/styles/index.scss +++ b/services/minespace-web/src/styles/index.scss @@ -35,6 +35,7 @@ @import "./components/SteppedForm.scss"; @import "./components/Tailings.scss"; @import "./components/Incidents.scss"; +@import "./components/FileReplace.scss"; // UTILITIES - utilities and helper classes. This layer has the highest specificity. @import "./generic/helpers.scss";