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  }