Browse Source

Fix bug in parser: turns out it is possible to have a single CS version embedded in multiple CORR versions.

Joeri Exelmans 2 years ago
parent
commit
b3160ac2bd

+ 18 - 18
src/frontend/versioned_model/correspondence.tsx

@@ -82,6 +82,12 @@ export function newCorrespondence({deltaRegistry, generateUUID, versionRegistry}
       }
     }));
 
+    // Helper
+    const filterCorrParents = (corrParentVersions: Version[]) => {
+      // when parsing/rendering, if one CORR-parent is a parent of another CORR-parent, then drop this one.
+      return corrParentVersions.filter(parent => !corrParentVersions.some(child => child !== parent && parent.findDescendant(child) !== undefined));
+    }
+
     // Reducer
 
     const parseExistingVersion = (csVersion: Version) => {
@@ -97,18 +103,18 @@ export function newCorrespondence({deltaRegistry, generateUUID, versionRegistry}
         const corrParentVersions = csParentVersion.getReverseEmbeddings("cs").filter(isPartOfThisCorrespondence);
         if (corrParentVersions.length === 0) {
           throw new Error("Assertion failed: CS has a parent, but this parent is not yet part of a CORR version.");
-          // console.log("Cannot parse CS version, because its parent is not parsed yet...");
-          // continue;
-        }
-        if (corrParentVersions.length > 1) {
-          throw new Error("Assertion failed: CS is part of multiple CORR versions (should be impossible, because parsing is deterministic)");
         }
-        const [corrParentVersion] = corrParentVersions;
+
+        // Even though parsing is deterministic, it is still possible that the same CS version is part of multiple CORR versions.
+        // For instance, when merging at the level of CS, and then rendering the change (with a conflict between the CORR-parents), results in a single CS version embedded in multiple CORR versions.
+
+        const filteredCorrParentVersions = filterCorrParents(corrParentVersions);
+        // And if then, there are still multiple CORR versions to choose from, we just pick one:
+        // (it would be better to ask the user which one, but whatever)
+        const [corrParentVersion] = filteredCorrParentVersions;
         const asParentVersion = corrParentVersion.getEmbedding("as").version;
         if (asParentVersion === undefined) {
           throw new Error("Assertion failed: CS's parent is part of a CORR version, but that CORR version does not embed an AS version.");
-          // console.log("Cannot parse - no parent AS version");
-          // continue;
         }
 
         const [csGS, corrGS, asGS] = [csParentVersion, corrParentVersion, asParentVersion].map(v => getGraphState(v));
@@ -138,18 +144,13 @@ export function newCorrespondence({deltaRegistry, generateUUID, versionRegistry}
           const corrParentVersions = asParentVersion.getReverseEmbeddings("as").filter(isPartOfThisCorrespondence);
           if (corrParentVersions.length === 0) {
             throw new Error("Assertion failed: AS has a parent, but this parent is not yet part of a CORR version.");
-            // console.log("Cannot parse - no parent CORR version");
-            // return;
           }
           // AS version may be embedded into multiple CORR versions
           // If one CORR version is a child of another (parent), then we only render based on the child:
-          const filteredCorrParentVersions = corrParentVersions.filter(parent => {
-            // if there exists a child version, remove the parent from resulting array:
-            return !corrParentVersions.some(child => child !== parent && parent.findDescendant(child) !== undefined);
-          });
+          const filteredCorrParentVersions = filterCorrParents(corrParentVersions);
           // And if then, there are still multiple CORR versions to choose from, we just pick one:
           // (it would be better to ask the user which one, but whatever)
-          const corrParentVersion = filteredCorrParentVersions[0]
+          const [corrParentVersion] = filteredCorrParentVersions;
           console.log({corrParentVersion, filteredCorrParentVersions, corrParentVersions});
           const csParentVersion = corrParentVersion.getEmbedding("cs").version;
           if (csParentVersion === undefined) {
@@ -198,7 +199,7 @@ export function newCorrespondence({deltaRegistry, generateUUID, versionRegistry}
             const csToAs = new Map();
             const asToCs = new Map();
 
-          for (const corrNode of corrGS.nodes.values()) {
+            for (const corrNode of corrGS.nodes.values()) {
               const csNode = corrNode.getOutgoingEdges().get("cs");
               if (csNode?.type !== "node") {
                 continue; // corrNode is not a correspondence node
@@ -236,8 +237,7 @@ export function newCorrespondence({deltaRegistry, generateUUID, versionRegistry}
         }
 
         if (asParentVersion.getReverseEmbeddings("as").filter(isPartOfThisCorrespondence).length === 0) {
-          return renderExistingVersion(asParentVersion, setManualRendererState)
-          .then(render);
+          return renderExistingVersion(asParentVersion, setManualRendererState).then(render);
         }
         else {
           return render();

+ 14 - 15
src/frontend/versioned_model/graph_view.tsx

@@ -5,6 +5,8 @@ import { GraphvizComponent } from '../graphviz';
 import {D3Graph, D3GraphData, D3NodeData, defaultGraphForces} from "../d3graph/d3graph";
 import {InfoHoverCardOverlay} from "../info_hover_card";
 import { D3OnionGraphData } from "../d3graph/reducers/onion_graph";
+import { D3GraphEditable, UserEditCallback } from "../d3graph/d3graph_editable";
+import { GraphState } from "onion/graph_state";
 
 function esc(str) {
   return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
@@ -56,6 +58,11 @@ interface GraphViewProps<N,L> {
   graphvizHelp?: JSX.Element;
   mouseUpHandler: (event, mouse:{x:number, y:number}, node?: D3NodeData<N>) => void;
   defaultRenderer: Renderer;
+  editorCallbacks?: {
+    graphState: GraphState,
+    setNextNodePosition: (newX: number, newY: number) => void,
+    onUserEdit: UserEditCallback,
+  };
   children?: any;
 }
 
@@ -72,17 +79,6 @@ export function GraphView<N,L>(props: GraphViewProps<N,L>) {
     }
   }
 
-  // const dot2 = `digraph {
-  //   bgcolor="transparent";
-  //   node[shape=record];
-  //   ${graphData.nodes.filter(node => !node.id.startsWith("V")).map(node => {
-  //     const outgoing = graphData.links.filter(link => (link.source.id || link.source) === node.id);
-  //     return `"${esc(node.id)}" [label="{ ${["", ...outgoing.map(o => `${o.label}`)].join('|')} } | { ${[esc(node.label), ...outgoing.map(o => (o.target.id || o.target).startsWith("V") ? `${esc(o.target.label)}` : `<L${o.label}>`)].join('|')} }", fillcolor="${node.color}", style="filled,rounded", ${node.bold?`penwidth=4.0,`:``} URL="javascript:${esc2(`graphvizClicked('${graphViewId: string, +node.id}', 2)`)}"]`;
-  //   }).join('\n')}
-
-  //   ${graphData.links.filter(link => !link.target.id.startsWith("V")).map(link => `"${esc(link.source.id || link.source)}":"L${link.label}" -> "${esc(link.target.id || link.target)}" [label="${esc(link.label)}", fontcolor="${link.color}", color="${link.color}"${link.bidirectional?`, dir=none`:``}]`).join('\n')}
-  // }`;
-
   // @ts-ignore:
   const graphviz = <Mantine.ScrollArea style={{backgroundColor:"#eee"}}>
       <GraphvizComponent dot={props.graphvizLayout(id, graphData)}
@@ -94,8 +90,8 @@ export function GraphView<N,L>(props: GraphViewProps<N,L>) {
       <Mantine.Tooltip withArrow openDelay={300} label={<Mantine.Text><b>D3</b>: Fast, poor layout<br/><b>Graphviz</b>: Slow, nice layout</Mantine.Text>}>
       <Mantine.SegmentedControl
         data={[
-          { label: 'D3', value: 'd3' },
-          { label: 'Graphviz', value: 'graphviz' },
+          { label: 'D3' + (props.editorCallbacks ? ' (editable)' : ''), value: 'd3' },
+          { label: 'Graphviz' + (props.editorCallbacks ? ' (read-only)' : ''), value: 'graphviz' },
         ]}
         value={renderer}
         onChange={setRenderer}
@@ -106,11 +102,14 @@ export function GraphView<N,L>(props: GraphViewProps<N,L>) {
     </Mantine.Group>
     {renderer==="d3"?
       <InfoHoverCardOverlay contents={help}>
-        <D3Graph graph={graphData} forces={defaultGraphForces} mouseUpHandler={mouseUpHandler} />
+        {props.editorCallbacks ?
+            <D3GraphEditable graph={graphData as D3OnionGraphData} forces={defaultGraphForces} graphState={props.editorCallbacks.graphState} setNextNodePosition={props.editorCallbacks.setNextNodePosition} onUserEdit={props.editorCallbacks.onUserEdit} />
+          : <D3Graph graph={graphData} forces={defaultGraphForces} mouseUpHandler={mouseUpHandler} />
+        }
       </InfoHoverCardOverlay>
       :
       props.graphvizHelp ?
-        <InfoHoverCardOverlay contents={props.graphvizHelp}>{graphviz}</InfoHoverCardOverlay>
+        <InfoHoverCardOverlay contents={props.help}>{graphviz}</InfoHoverCardOverlay>
         : graphviz
     }
   </Mantine.Stack>;

+ 12 - 14
src/frontend/versioned_model/single_model.tsx

@@ -219,20 +219,18 @@ export function newOnion({readonly, deltaRegistry, versionRegistry}) {
 
     const callbacks = Object.assign({}, defaultCallbacks, overridenCallbacks(reducer));
 
-    const graphStateComponent = readonly ? 
-          <GraphView
-            defaultRenderer="graphviz"
-            graphvizLayout={objectLikeGraphVizLayout}
-            graphData={graph} help={helpText.graphEditorReadonly} mouseUpHandler={()=>{}} />
-        : <InfoHoverCardOverlay contents={helpText.graphEditor}>
-            <D3GraphEditable
-              graph={graph}
-              graphState={graphState}
-              forces={defaultGraphForces}
-              setNextNodePosition={(newX,newY) => {x = newX; y = newY;}}
-              onUserEdit={callbacks.onUserEdit}
-            />
-          </InfoHoverCardOverlay>;
+    const graphStateComponent = <GraphView
+      defaultRenderer={readonly ? "graphviz" : "d3"}
+      graphvizLayout={objectLikeGraphVizLayout}
+      graphData={graph}
+      help={readonly ? helpText.graphEditorReadonly: helpText.graphEditor}
+      mouseUpHandler={()=>{}}
+      editorCallbacks={readonly ? undefined : {
+        graphState,
+        setNextNodePosition: (newX,newY) => {x = newX; y = newY;},
+        onUserEdit: callbacks.onUserEdit,
+      }}
+    />;
 
     const deltaComponentProps = {
       graphvizLayout: genericGraphVizLayout,