github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/webui/src/lib/components/repository/ObjectsDiff.jsx (about) 1 import React from "react"; 2 import {useAPI} from "../../hooks/api"; 3 import {objects} from "../../api"; 4 import ReactDiffViewer, {DiffMethod} from "react-diff-viewer-continued"; 5 import {AlertError, Loading} from "../controls"; 6 import {humanSize} from "./tree"; 7 import Alert from "react-bootstrap/Alert"; 8 import {InfoIcon} from "@primer/octicons-react"; 9 import {useStorageConfig} from "../../hooks/storageConfig"; 10 11 const maxDiffSizeBytes = 120 << 10; 12 const supportedReadableFormats = ["txt", "text", "csv", "tsv", "yaml", "yml", "json"]; 13 14 export const ObjectsDiff = ({diffType, repoId, leftRef, rightRef, path}) => { 15 const config = useStorageConfig(); 16 const readable = readableObject(path); 17 let left; 18 let right; 19 switch (diffType) { 20 case 'changed': 21 case 'conflict': 22 left = useAPI(async () => objects.getStat(repoId, leftRef, path), 23 [repoId, leftRef, path]); 24 right = useAPI(async () => objects.getStat(repoId, rightRef, path), 25 [repoId, rightRef, path]); 26 break; 27 case 'added': 28 right = useAPI(async () => objects.getStat(repoId, rightRef, path), 29 [repoId, rightRef, path]); 30 break; 31 case 'removed': 32 left = useAPI(async () => objects.getStat(repoId, leftRef, path), 33 [repoId, leftRef, path]); 34 break; 35 default: 36 return <AlertError error={"Unsupported diff type " + diffType}/>; 37 } 38 39 if ((left && left.loading) || (right && right.loading)) return <Loading/>; 40 const err = (left && left.error) || (right && right.err); 41 if (err) return <AlertError error={err}/>; 42 43 const leftStat = left && left.response; 44 const rightStat = right && right.response; 45 if (!readable) { 46 return <NoContentDiff left={leftStat} right={rightStat} diffType={diffType}/>; 47 } 48 const objectTooBig = (leftStat && leftStat.size_bytes > maxDiffSizeBytes) || (rightStat && rightStat.size_bytes > maxDiffSizeBytes); 49 if (objectTooBig) { 50 return <AlertError error={path + " is too big (> " + humanSize(maxDiffSizeBytes)+ "). To view its diff please download" + 51 " the objects and use an external diff tool."}/> 52 } 53 const leftSize = leftStat && leftStat.size_bytes; 54 const rightSize = rightStat && rightStat.size_bytes; 55 return <ContentDiff config={config} repoId={repoId} path={path} leftRef={left && leftRef} rightRef={right && rightRef} 56 leftSize={leftSize} rightSize={rightSize} diffType={diffType}/>; 57 } 58 59 function readableObject(path) { 60 for (const ext of supportedReadableFormats) { 61 if (path.endsWith("." + ext)) { 62 return true; 63 } 64 } 65 return false; 66 } 67 68 const NoContentDiff = ({left, right, diffType}) => { 69 const supportedFileExtensions = supportedReadableFormats.map((fileType) => `.${fileType}`); 70 return <div> 71 <span><StatDiff left={left} right={right} diffType={diffType}/></span> 72 <span><Alert variant="light"><InfoIcon/> {`lakeFS supports content diff for ${supportedFileExtensions.join(',')} file formats only`}</Alert></span> 73 </div>; 74 } 75 76 const ContentDiff = ({config, repoId, path, leftRef, rightRef, leftSize, rightSize, diffType}) => { 77 const left = leftRef && useAPI(async () => objects.get(repoId, leftRef, path, config.pre_sign_support_ui), 78 [repoId, leftRef, path]); 79 const right = rightRef && useAPI(async () => objects.get(repoId, rightRef, path, config.pre_sign_support_ui), 80 [repoId, rightRef, path]); 81 82 if ((left && left.loading) || (right && right.loading)) return <Loading/>; 83 const err = (left && left.error) || (right && right.err); 84 if (err) return <AlertError error={err}/>; 85 86 return <div> 87 <span><DiffSizeReport leftSize={leftSize} rightSize={rightSize} diffType={diffType}/></span> 88 <ReactDiffViewer 89 oldValue={left && left.response} 90 newValue={right && right.response} 91 splitView={false} 92 compareMethod={DiffMethod.WORDS}/> 93 </div>; 94 } 95 96 function validateDiffInput(left, right, diffType) { 97 switch (diffType) { 98 case 'changed': 99 if (!left && !right) return <AlertError error={"Invalid diff input"}/>; 100 break; 101 case 'added': 102 if (!right) return <AlertError error={"Invalid diff input: right hand-side is missing"}/>; 103 break; 104 case 'removed': 105 if (!left) return <AlertError error={"Invalid diff input: left hand-side is missing"}/>; 106 break; 107 case 'conflict': 108 break; 109 default: 110 return <AlertError error={"Unknown diff type: " + diffType}/>; 111 } 112 } 113 114 const StatDiff = ({left, right, diffType}) => { 115 const err = validateDiffInput(left, right, diffType); 116 if (err) return err; 117 const rightSize = right && right.size_bytes; 118 const leftSize = left && left.size_bytes; 119 return <> 120 <div className={"stats-diff-block"}> 121 <DiffSizeReport leftSize={leftSize} rightSize={rightSize} diffType={diffType}/> 122 </div> 123 </>; 124 } 125 126 const DiffSizeReport = ({leftSize, rightSize, diffType}) => { 127 let label = diffType; 128 let size; 129 switch (diffType) { 130 case 'changed': 131 size = leftSize - rightSize; 132 if (size === 0) { 133 return <div> 134 <span className="unchanged">identical file size</span> 135 </div>; 136 } 137 if (size < 0) { 138 size = -size; 139 label = "added"; 140 } else { 141 label = "removed"; 142 } 143 break; 144 case 'conflict': // conflict will compare left and right. further details: https://github.com/treeverse/lakeFS/issues/3269 145 return <div> 146 <span className={label}>{label} </span> 147 <span>both source and destination file were changed.</span> 148 <span className={"diff-size"}> Source: {humanSize(leftSize)}</span> 149 <span> in size, </span> 150 <span className={"diff-size"}> Destination: {humanSize(rightSize)}</span> 151 <span> in size</span> 152 </div>; 153 case 'added': 154 size = rightSize; 155 break; 156 case 'removed': 157 size = leftSize; 158 break; 159 default: 160 return <AlertError error={"Unknown diff type: " + diffType}/>; 161 } 162 163 164 return <div> 165 <span className={label}>{label} </span> 166 <span className={"diff-size"}>{humanSize(size)}</span> 167 <span> in size</span> 168 </div>; 169 }