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

dataunitylab / relational-playground / #110

11 Sep 2025 02:31PM UTC coverage: 77.346% (-1.2%) from 78.51%
#110

push

michaelmior
Remove deprecated ReactDOM.findDOMNode

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

524 of 741 branches covered (70.72%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

40 existing lines in 10 files now uncovered.

1009 of 1241 relevant lines covered (81.31%)

14408.74 hits per line

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

76.6
/src/RelExpr.js
1
// @flow
2
import React, {useEffect, useRef, useState} from 'react';
3
import {
4
  UnaryRelOp,
5
  Projection,
6
  Rename,
7
  Selection,
8
  BinaryRelOp,
9
  Except,
10
  OrderBy,
11
  Product,
12
  Join,
13
  Intersect,
14
  Union,
15
  GroupBy,
16
} from './RelOp';
17
import Relation from './Relation';
18
import {exprToString} from './util';
19
import {changeExpr} from './modules/data';
20
import {useReactGA} from './contexts/ReactGAContext';
21

22
import './RelExpr.css';
23

24
import type {Node, StatelessFunctionalComponent} from 'react';
25
import {useSelector} from 'react-redux';
26
import type {State} from './modules/relexp';
27

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

34
/** A graphical representation of a relational algebra expression */
35
const RelExpr: StatelessFunctionalComponent<Props> = (props) => {
3✔
36
  const nodeRef = useRef<?HTMLSpanElement>();
37✔
37
  const currentNode = useSelector<{data: State}, _>((state) => state.data.expr);
62✔
38
  const [isHovering, setIsHovering] = useState(false);
36✔
39
  const [isSelected, setIsSelected] = useState(false);
36✔
40
  const contextReactGA = useReactGA();
36✔
41
  const ReactGA = props.ReactGA || contextReactGA;
36✔
42

43
  useEffect(() => {
36✔
44
    // Adjust 'clicked' highlighting based on any changes to currentNode selected
45
    const clicked = props.expr === currentNode;
25✔
46
    if (!clicked) {
25!
47
      setIsSelected(false);
25✔
48
      setIsHovering(false);
25✔
49
    }
50
  }, [currentNode, props.expr]);
51

52
  /**
53
   * @param expr - a relational algebra expression object to render
54
   * @return a component representing the top-most expression
55
   */
56
  const buildExpr = (expr: {[string]: any}): Node => {
36✔
57
    // Don't try to render empty expressions
58
    if (!expr || Object.keys(expr).length === 0) {
36!
59
      return '';
×
60
    }
61
    const type = Object.keys(expr)[0];
36✔
62
    switch (type) {
36!
63
      case 'projection':
64
        return (
1✔
65
          <span>
66
            <UnaryRelOp
67
              operator={
68
                <Projection project={expr.projection.arguments.project} />
69
              }
70
            >
71
              <RelExpr
72
                expr={expr.projection.children[0]}
73
                changeExpr={props.changeExpr}
74
              />
75
            </UnaryRelOp>
76
          </span>
77
        );
78

79
      case 'selection':
80
        return (
16✔
81
          <span>
82
            <UnaryRelOp
83
              operator={
84
                <Selection
85
                  select={exprToString(expr.selection.arguments.select)}
86
                />
87
              }
88
            >
89
              <RelExpr
90
                expr={expr.selection.children[0]}
91
                changeExpr={props.changeExpr}
92
              />
93
            </UnaryRelOp>
94
          </span>
95
        );
96

97
      case 'rename':
98
        return (
1✔
99
          <span>
100
            <UnaryRelOp
101
              operator={<Rename rename={expr.rename.arguments.rename} />}
102
            >
103
              <RelExpr
104
                expr={expr.rename.children[0]}
105
                changeExpr={props.changeExpr}
106
              />
107
            </UnaryRelOp>
108
          </span>
109
        );
110

111
      case 'relation':
112
        return <Relation name={expr.relation} />;
17✔
113

114
      case 'order_by':
115
        return (
×
116
          <OrderBy
117
            columns={expr.order_by.arguments.order_by}
118
            relation={buildExpr(expr.order_by.children[0])}
119
          />
120
        );
121

122
      case 'group_by':
123
        return (
×
124
          <span>
125
            <UnaryRelOp
126
              operator={
127
                <GroupBy
128
                  groupBy={expr.group_by.arguments.groupBy}
129
                  aggregates={expr.group_by.arguments.aggregates.map(
130
                    (agg) =>
131
                      `${agg.aggregate.function}(${agg.aggregate.column})`
×
132
                  )}
133
                  selectColumns={expr.group_by.arguments.selectColumns}
134
                />
135
              }
136
            >
137
              <RelExpr
138
                expr={expr.group_by.children[0]}
139
                changeExpr={props.changeExpr}
140
              />
141
            </UnaryRelOp>
142
          </span>
143
        );
144

145
      case 'join':
146
        return (
×
147
          <Join
148
            type={expr.join.type}
149
            condition={exprToString(expr.join.condition)}
150
            left={buildExpr(expr.join.left)}
151
            right={buildExpr(expr.join.right)}
152
          />
153
        );
154

155
      case 'except':
156
      case 'intersect':
157
      case 'product':
158
      case 'union':
159
        const operator = {
×
160
          except: <Except />,
161
          intersect: <Intersect />,
162
          product: <Product />,
163
          union: <Union />,
164
        }[type];
165
        return (
×
166
          <BinaryRelOp
167
            operator={operator}
168
            left={
169
              <RelExpr expr={expr[type].left} changeExpr={props.changeExpr} />
170
            }
171
            right={
172
              <RelExpr expr={expr[type].right} changeExpr={props.changeExpr} />
173
            }
174
          />
175
        );
176

177
      default:
178
        throw new Error('Invalid expression ' + JSON.stringify(expr) + '.');
1✔
179
    }
180
  };
181

182
  /**
183
   * @param e - the event object which generated the click
184
   */
185
  const handleExprClick = (e: SyntheticMouseEvent<HTMLElement>): void => {
36✔
186
    e.stopPropagation();
1✔
187
    const node = nodeRef.current;
1✔
188

189
    const clicked = e.type === 'click' && !isSelected;
1✔
190

191
    if (props.changeExpr && node instanceof HTMLElement) {
1!
192
      if (clicked) {
1!
193
        props.changeExpr(props.expr, node);
1✔
194
      } else {
195
        props.changeExpr({}, node);
×
196
      }
197

198
      setIsHovering(false);
1✔
199
      setIsSelected(clicked);
1✔
200
    }
201

202
    if (ReactGA) {
1!
203
      ReactGA.event({
1✔
204
        category: 'User Selecting Relational Algebra Enclosure',
205
        action: Object.keys(props.expr)[0],
206
      });
207
    }
208
  };
209

210
  const handleExprHover = (e: SyntheticMouseEvent<HTMLElement>): void => {
36✔
211
    e.stopPropagation();
2✔
212
    setIsHovering(e.type === 'mouseover');
2✔
213
  };
214

215
  if (!props.expr || Object.keys(props.expr).length === 0) {
36!
UNCOV
216
    return '';
×
217
  }
218
  const type = Object.keys(props.expr)[0];
36✔
219
  let className = 'RelExpr';
36✔
220
  if (isHovering) {
36✔
221
    className += ' hovering';
2✔
222
  }
223
  if (isSelected) {
36✔
224
    className += ' clicked';
2✔
225
  }
226
  if (type === 'relation') {
36✔
227
    return (
17✔
228
      <span className="RelExpr" style={{margin: '.4em'}}>
229
        {buildExpr(props.expr)}
230
      </span>
231
    );
232
  } else {
233
    return (
19✔
234
      <span
235
        className={className}
236
        onMouseOver={handleExprHover}
237
        onMouseOut={handleExprHover}
238
        onClick={handleExprClick}
239
        style={{margin: '.4em'}}
240
        ref={nodeRef}
241
      >
242
        {buildExpr(props.expr)}
243
      </span>
244
    );
245
  }
246
};
247

248
export default RelExpr;
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