• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

dataunitylab / relational-playground / #109

10 Sep 2025 06:35PM UTC coverage: 78.51% (+0.1%) from 78.407%
#109

push

michaelmior
Fix SqlEditor test for React context refactoring

Signed-off-by: Michael Mior <mmior@mail.rit.edu>

526 of 743 branches covered (70.79%)

Branch coverage included in aggregate %.

1034 of 1244 relevant lines covered (83.12%)

14373.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.53
/src/RelExprTree.js
1
// @flow
2
import React from 'react';
3
import TreeMenu, {ItemComponent} from 'react-simple-tree-menu';
4
import {v4 as uuidv4} from 'uuid';
5
import {
6
  Projection,
7
  Rename,
8
  Selection,
9
  Except,
10
  Product,
11
  Join,
12
  Intersect,
13
  Union,
14
  GroupBy,
15
} from './RelOp';
16
import {exprToString} from './util';
17
import {changeExpr} from './modules/data';
18
import {useReactGA} from './contexts/ReactGAContext';
19

20
import '../node_modules/react-simple-tree-menu/dist/main.css';
21
import './RelExprTree.css';
22

23
import type {Node, StatelessFunctionalComponent} from 'react';
24

25
type Props = {
26
  changeExpr: typeof changeExpr,
27
  expr: {[string]: any},
28
  ReactGA?: any, // For backwards compatibility with tests
29
};
30

31
/** A graphical representation of a relational algebra expression */
32
const RelExprTree: StatelessFunctionalComponent<Props> = (props) => {
3✔
33
  const contextReactGA = useReactGA();
12✔
34
  const ReactGA = props.ReactGA || contextReactGA;
11✔
35
  /**
36
   * @param expr - a relational algebra expression object to render
37
   * @param keys - an array where all created paths should be saved
38
   * @param path - the path to the current node
39
   * @return a tree structure representing the exppression
40
   */
41
  const buildTree = (
11✔
42
    expr: {[string]: any},
43
    keys: Array<string>,
44
    path: Array<string> = []
11✔
45
  ): {} => {
46
    // Don't try to render empty expressions
47
    if (!expr || Object.keys(expr).length === 0) {
22!
48
      return {};
×
49
    }
50

51
    // Save the constructed path so we can set this open later
52
    const key = uuidv4();
22✔
53
    const newPath = [...path, key];
22✔
54
    keys.push(newPath.join('/'));
22✔
55

56
    const type = Object.keys(expr)[0];
22✔
57
    switch (type) {
22!
58
      case 'projection':
59
      case 'selection':
60
      case 'rename':
61
      case 'group_by':
62
        return {
11✔
63
          key: key,
64
          expr: expr,
65
          nodes: [buildTree(expr[type].children[0], keys, newPath)],
66
        };
67
      case 'relation':
68
        return {
10✔
69
          key: key,
70
          expr: expr,
71
        };
72
      case 'except':
73
      case 'intersect':
74
      case 'join':
75
      case 'product':
76
      case 'union':
77
        return {
×
78
          key: key,
79
          expr: expr,
80
          nodes: [
81
            buildTree(expr[type].left, keys, newPath),
82
            buildTree(expr[type].right, keys, newPath),
83
          ],
84
        };
85
      default:
86
        throw new Error('Invalid expression ' + JSON.stringify(expr) + '.');
1✔
87
    }
88
  };
89

90
  const getLabel = (expr: {[string]: any}): Node => {
11✔
91
    if (!expr || Object.keys(expr).length === 0) {
23!
92
      return '';
×
93
    }
94

95
    const type = Object.keys(expr)[0];
23✔
96
    switch (type) {
23!
97
      case 'projection':
98
        return <Projection project={expr.projection.arguments.project} />;
1✔
99
      case 'selection':
100
        return (
10✔
101
          <Selection
102
            select={exprToString(
103
              expr.selection.arguments.select ||
10!
104
                expr.selection.arguments.condition
105
            )}
106
          />
107
        );
108
      case 'rename':
109
        return <Rename rename={expr.rename.arguments.rename} />;
1✔
110
      case 'group_by':
111
        return (
×
112
          <GroupBy
113
            groupBy={expr.group_by.arguments.groupBy}
114
            aggregates={expr.group_by.arguments.aggregates.map(
115
              (agg) => `${agg.aggregate.function}(${agg.aggregate.column})`
×
116
            )}
117
            selectColumns={expr.group_by.arguments.selectColumns}
118
          />
119
        );
120
      case 'relation':
121
        return expr.relation;
11✔
122
      case 'join':
123
        return (
×
124
          <Join
125
            type={expr.join.type}
126
            condition={exprToString(expr.join.condition)}
127
          />
128
        );
129
      case 'except':
130
      case 'intersect':
131
      case 'product':
132
      case 'union':
133
        const operator = {
×
134
          except: <Except />,
135
          intersect: <Intersect />,
136
          product: <Product />,
137
          union: <Union />,
138
        }[type];
139
        return operator;
×
140
      default:
141
        throw new Error('Invalid expression ' + JSON.stringify(expr) + '.');
×
142
    }
143
  };
144

145
  const keys: Array<string> = [];
11✔
146
  const data = [buildTree(props.expr, keys)];
11✔
147
  return (
10✔
148
    <div className="RelExprTree">
149
      <TreeMenu
150
        data={data}
151
        initialOpenNodes={keys}
152
        hasSearch={false}
153
        disableKeyboard
154
        onClickItem={(clickProps) => {
155
          props.changeExpr(clickProps.expr, null);
1✔
156

157
          if (ReactGA) {
1!
158
            ReactGA.event({
1✔
159
              category: 'User Selecting Relational Algebra Tree',
160
              action: Object.keys(clickProps.expr)[0],
161
            });
162
          }
163
        }}
164
      >
165
        {({search, items}) => (
166
          <div>
11✔
167
            {items.map(({key, ...props}) => {
168
              const newProps = {label: getLabel(props.expr)};
23✔
169
              Object.assign(props, newProps);
23✔
170
              return <ItemComponent key={key} {...props} />;
23✔
171
            })}
172
          </div>
173
        )}
174
      </TreeMenu>
175
    </div>
176
  );
177
};
178

179
export default RelExprTree;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc