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

dataunitylab / relational-playground / #107

09 Sep 2025 03:15PM UTC coverage: 78.436% (-7.9%) from 86.296%
#107

push

michaelmior
Update Node to 24

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

517 of 731 branches covered (70.73%)

Branch coverage included in aggregate %.

1018 of 1226 relevant lines covered (83.03%)

14584.19 hits per line

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

66.2
/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

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

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

24
type Props = {
25
  changeExpr: typeof changeExpr,
26
  expr: {[string]: any},
27
  ReactGA: any,
28
};
29

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

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

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

87
  const getLabel = (expr: {[string]: any}): Node => {
12✔
88
    if (!expr || Object.keys(expr).length === 0) {
23!
89
      return '';
×
90
    }
91

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

137
  const keys: Array<string> = [];
12✔
138
  const data = [buildTree(props.expr, keys)];
12✔
139
  return (
10✔
140
    <div className="RelExprTree">
141
      <TreeMenu
142
        data={data}
143
        initialOpenNodes={keys}
144
        hasSearch={false}
145
        disableKeyboard
146
        onClickItem={(clickProps) => {
147
          props.changeExpr(clickProps.expr, null);
1✔
148

149
          if (props.ReactGA) {
1!
150
            props.ReactGA.event({
1✔
151
              category: 'User Selecting Relational Algebra Tree',
152
              action: Object.keys(clickProps.expr)[0],
153
            });
154
          }
155
        }}
156
      >
157
        {({search, items}) => (
158
          <div>
11✔
159
            {items.map(({key, ...props}) => {
160
              const newProps = {label: getLabel(props.expr)};
23✔
161
              Object.assign(props, newProps);
23✔
162
              return <ItemComponent key={key} {...props} />;
23✔
163
            })}
164
          </div>
165
        )}
166
      </TreeMenu>
167
    </div>
168
  );
169
};
170

171
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