datajunction-ui 0.0.1-rc.1 → 0.0.1-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.github/workflows/ci.yml +3 -3
  2. package/.prettierignore +3 -1
  3. package/package.json +16 -8
  4. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +67 -25
  5. package/src/app/components/NamespaceHeader.jsx +4 -13
  6. package/src/app/components/QueryInfo.jsx +109 -0
  7. package/src/app/components/Tab.jsx +1 -8
  8. package/src/app/components/ToggleSwitch.jsx +17 -0
  9. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  10. package/src/app/components/djgraph/Collapse.jsx +46 -0
  11. package/src/app/components/djgraph/DJNode.jsx +56 -80
  12. package/src/app/components/djgraph/DJNodeColumns.jsx +68 -0
  13. package/src/app/components/djgraph/DJNodeDimensions.jsx +69 -0
  14. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +82 -43
  15. package/src/app/icons/CollapsedIcon.jsx +15 -0
  16. package/src/app/icons/ExpandedIcon.jsx +15 -0
  17. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  18. package/src/app/icons/InvalidIcon.jsx +14 -0
  19. package/src/app/icons/PythonIcon.jsx +52 -0
  20. package/src/app/icons/TableIcon.jsx +14 -0
  21. package/src/app/icons/ValidIcon.jsx +14 -0
  22. package/src/app/index.tsx +28 -15
  23. package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
  24. package/src/app/pages/NamespacePage/Loadable.jsx +9 -7
  25. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +39 -17
  26. package/src/app/pages/NamespacePage/index.jsx +90 -28
  27. package/src/app/pages/NodePage/ClientCodePopover.jsx +30 -0
  28. package/src/app/pages/NodePage/Loadable.jsx +9 -7
  29. package/src/app/pages/NodePage/NodeColumnTab.jsx +36 -20
  30. package/src/app/pages/NodePage/NodeGraphTab.jsx +83 -54
  31. package/src/app/pages/NodePage/NodeHistory.jsx +71 -0
  32. package/src/app/pages/NodePage/NodeInfoTab.jsx +132 -49
  33. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +151 -0
  34. package/src/app/pages/NodePage/NodeSQLTab.jsx +100 -0
  35. package/src/app/pages/NodePage/NodeStatus.jsx +14 -20
  36. package/src/app/pages/NodePage/index.jsx +49 -13
  37. package/src/app/pages/Root/index.tsx +5 -0
  38. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  39. package/src/app/pages/SQLBuilderPage/index.jsx +344 -0
  40. package/src/app/providers/djclient.jsx +5 -0
  41. package/src/app/services/DJService.js +125 -1
  42. package/src/styles/dag.css +111 -5
  43. package/src/styles/index.css +343 -25
  44. package/tsconfig.json +1 -1
  45. package/webpack.config.js +22 -6
  46. package/.babelrc +0 -4
  47. package/.env.local +0 -4
  48. package/.env.production +0 -1
  49. package/.vscode/extensions.json +0 -7
  50. package/.vscode/launch.json +0 -15
  51. package/.vscode/settings.json +0 -25
  52. package/Dockerfile +0 -7
  53. package/dist/5fa71a03d45dc2e3d61447f3013a303d.png +0 -0
  54. package/dist/index.html +0 -26
  55. package/dist/main.js +0 -23303
  56. package/dist/vendor.js +0 -281
  57. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  58. package/src/app/pages/ListNamespacesPage/index.jsx +0 -52
@@ -0,0 +1,344 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import NamespaceHeader from '../../components/NamespaceHeader';
3
+ import { DataJunctionAPI } from '../../services/DJService';
4
+ import DJClientContext from '../../providers/djclient';
5
+ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
6
+ import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
7
+ import { format } from 'sql-formatter';
8
+ import Select from 'react-select';
9
+ import QueryInfo from '../../components/QueryInfo';
10
+
11
+ export function SQLBuilderPage() {
12
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
13
+ const [stagedMetrics, setStagedMetrics] = useState([]);
14
+ const [metrics, setMetrics] = useState([]);
15
+ const [commonDimensionsList, setCommonDimensionsList] = useState([]);
16
+ const [selectedDimensions, setSelectedDimensions] = useState([]);
17
+ const [stagedDimensions, setStagedDimensions] = useState([]);
18
+ const [selectedMetrics, setSelectedMetrics] = useState([]);
19
+ const [query, setQuery] = useState('');
20
+ const [submittedQueryInfo, setSubmittedQueryInfo] = useState(null);
21
+ const [data, setData] = useState(null);
22
+ const [loadingData, setLoadingData] = useState(false);
23
+ const [viewData, setViewData] = useState(false);
24
+ const [showHelp, setShowHelp] = useState(true);
25
+ const [showNumRows, setShowNumRows] = useState(100);
26
+ const [displayedRows, setDisplayedRows] = useState(<></>);
27
+ const numRowsOptions = [
28
+ {
29
+ value: 10,
30
+ label: '10 Rows',
31
+ isFixed: true,
32
+ },
33
+ {
34
+ value: 100,
35
+ label: '100 Rows',
36
+ isFixed: true,
37
+ },
38
+ {
39
+ value: 1000,
40
+ label: '1,000 Rows',
41
+ isFixed: true,
42
+ },
43
+ ];
44
+ const toggleViewData = () => setViewData(current => !current);
45
+
46
+ // Get data for the current selection of metrics and dimensions
47
+ const getData = () => {
48
+ setLoadingData(true);
49
+ const fetchData = async () => {
50
+ setData(null);
51
+ const queryInfo = await djClient.data(
52
+ selectedMetrics,
53
+ selectedDimensions,
54
+ );
55
+ setLoadingData(false);
56
+ setSubmittedQueryInfo(queryInfo);
57
+ queryInfo.numRows = 0;
58
+ if (queryInfo.results && queryInfo.results?.length) {
59
+ setData(queryInfo.results);
60
+ queryInfo.numRows = queryInfo.results[0].rows.length;
61
+ setViewData(true);
62
+ setShowNumRows(100);
63
+ }
64
+ };
65
+ fetchData().catch(console.error);
66
+ };
67
+
68
+ const resetView = () => {
69
+ setQuery('');
70
+ setData(null);
71
+ setViewData(false);
72
+ };
73
+ const handleMetricSelect = event => {
74
+ const metrics = event.map(m => m.value);
75
+ resetView();
76
+ setStagedMetrics(metrics);
77
+ };
78
+
79
+ const handleMetricSelectorClose = () => {
80
+ setSelectedMetrics(stagedMetrics);
81
+ };
82
+
83
+ const handleDimensionSelect = event => {
84
+ const dimensions = event.map(d => d.value);
85
+ resetView();
86
+ setStagedDimensions(dimensions);
87
+ };
88
+
89
+ const handleDimensionSelectorClose = () => {
90
+ setSelectedDimensions(stagedDimensions);
91
+ };
92
+
93
+ // Get metrics
94
+ useEffect(() => {
95
+ const fetchData = async () => {
96
+ const metrics = await djClient.metrics();
97
+ setMetrics(metrics.map(m => ({ value: m, label: m })));
98
+ };
99
+ fetchData().catch(console.error);
100
+ }, [djClient, djClient.metrics]);
101
+
102
+ // Get common dimensions
103
+ useEffect(() => {
104
+ const fetchData = async () => {
105
+ if (selectedMetrics.length) {
106
+ const commonDimensions = await djClient.commonDimensions(
107
+ selectedMetrics,
108
+ );
109
+ setCommonDimensionsList(
110
+ commonDimensions.map(d => ({
111
+ value: d.name,
112
+ label: d.name,
113
+ path: d.path.join(' ▶ '),
114
+ })),
115
+ );
116
+ } else {
117
+ setCommonDimensionsList([]);
118
+ }
119
+ };
120
+ fetchData().catch(console.error);
121
+ }, [selectedMetrics, djClient]);
122
+
123
+ // Get SQL
124
+ useEffect(() => {
125
+ const fetchData = async () => {
126
+ if (selectedMetrics.length && selectedDimensions.length) {
127
+ const query = await djClient.sqls(selectedMetrics, selectedDimensions);
128
+ setShowHelp(false);
129
+ setQuery(query);
130
+ } else {
131
+ resetView();
132
+ setShowHelp(true);
133
+ }
134
+ };
135
+ fetchData().catch(console.error);
136
+ }, [selectedMetrics, selectedDimensions, djClient]);
137
+
138
+ // Set number of rows to display
139
+ useEffect(() => {
140
+ if (data) {
141
+ setDisplayedRows(
142
+ data[0].rows.slice(0, showNumRows).map((rowData, index) => (
143
+ <tr key={`data-row:${index}`}>
144
+ {rowData.map(rowValue => (
145
+ <td key={rowValue}>{rowValue}</td>
146
+ ))}
147
+ </tr>
148
+ )),
149
+ );
150
+ }
151
+ }, [showNumRows, data]);
152
+ const formatOptionLabel = ({ value, label, path }) => (
153
+ <div className={`badge dimension_option`}>
154
+ <div>{label}</div>
155
+ <span className={`badge dimension_option_subheading`}>{path}</span>
156
+ </div>
157
+ );
158
+
159
+ // @ts-ignore
160
+ return (
161
+ <>
162
+ <div className="mid">
163
+ <NamespaceHeader namespace="" />
164
+ <div className="card">
165
+ <div className="card-header">
166
+ <h4>Metrics</h4>
167
+ <Select
168
+ name="metrics"
169
+ options={metrics}
170
+ noOptionsMessage={() => 'No metrics found.'}
171
+ placeholder={`${metrics.length} Available Metrics`}
172
+ isMulti
173
+ isClearable
174
+ closeMenuOnSelect={false}
175
+ onChange={handleMetricSelect}
176
+ onMenuClose={handleMetricSelectorClose}
177
+ />
178
+ <h4>Group By</h4>
179
+ <Select
180
+ name="dimensions"
181
+ formatOptionLabel={formatOptionLabel}
182
+ options={commonDimensionsList}
183
+ noOptionsMessage={() =>
184
+ 'No shared dimensions found. Try selecting different metrics.'
185
+ }
186
+ placeholder={`${commonDimensionsList.length} Shared Dimensions`}
187
+ isMulti
188
+ isClearable
189
+ closeMenuOnSelect={false}
190
+ onChange={handleDimensionSelect}
191
+ onMenuClose={handleDimensionSelectorClose}
192
+ />
193
+ <h4>Filter By</h4>
194
+ <Select
195
+ name="filters"
196
+ className="filters"
197
+ formatOptionLabel={formatOptionLabel}
198
+ options={commonDimensionsList}
199
+ noOptionsMessage={() =>
200
+ 'No shared dimensions found. Try selecting different metrics.'
201
+ }
202
+ placeholder={`${commonDimensionsList.length} Shared Dimensions`}
203
+ isClearable
204
+ closeMenuOnSelect={true}
205
+ // onChange={handleDimensionSelect}
206
+ // onMenuClose={handleDimensionSelectorClose}
207
+ />
208
+ <Select
209
+ name="filters"
210
+ className="filters-operator"
211
+ // formatOptionLabel={formatOptionLabel}
212
+ options={[{value: '=', label: '='}, {value: '>', label: '>'}, {value: '>', label: '>'}]}
213
+ placeholder={`Operator`}
214
+ closeMenuOnSelect={true}
215
+ // onChange={handleDimensionSelect}
216
+ // onMenuClose={handleDimensionSelectorClose}
217
+ />
218
+ <input value={`Value`}></input>
219
+ </div>
220
+ <div className="card-header">
221
+ {showHelp ? (
222
+ <div className="card-light-shadow">
223
+ <h6>Using the SQL Builder</h6>
224
+ <p>
225
+ The sql builder allows you to group multiple metrics along
226
+ with their shared dimensions. Using your selections,
227
+ DataJunction will generate the corresponding SQL.
228
+ </p>
229
+ <ol>
230
+ <li>
231
+ <b>Select Metrics:</b> Start by selecting one or more
232
+ metrics from the metrics dropdown.
233
+ </li>
234
+ <li>
235
+ <b>Select Dimensions:</b> Next, select the dimension
236
+ attributes you would like to include. As you select
237
+ additional metrics, the list of available dimensions will be
238
+ filtered to those shared by the selected metrics. If the
239
+ dimensions list is empty, no shared dimensions were
240
+ discovered.
241
+ </li>
242
+ <li>
243
+ <b>View the generated SQL Query:</b> As you make your
244
+ selections, the SQL required to retrieve the set of metrics
245
+ and dimensions will be generated below.
246
+ </li>
247
+ <li>
248
+ <b>Run the Query:</b> If query running is enabled by your
249
+ server, you can also run the generated SQL query to view a
250
+ sample of 100 records.
251
+ </li>
252
+ </ol>
253
+ </div>
254
+ ) : (
255
+ <></>
256
+ )}
257
+ {query ? (
258
+ <>
259
+ {loadingData ? (
260
+ <span className="button-3 executing-button">
261
+ {'Running Query'}
262
+ </span>
263
+ ) : (
264
+ <span className="button-3 execute-button" onClick={getData}>
265
+ {'Run Query'}
266
+ </span>
267
+ )}
268
+ {data ? (
269
+ viewData ? (
270
+ <>
271
+ <span
272
+ className="button-3 neutral-button"
273
+ onClick={toggleViewData}
274
+ >
275
+ {'View Query'}
276
+ </span>
277
+ <span style={{ display: 'inline-block' }}>
278
+ <Select
279
+ name="num-rows"
280
+ defaultValue={numRowsOptions[1]}
281
+ options={numRowsOptions}
282
+ onChange={e => setShowNumRows(e.value)}
283
+ />
284
+ </span>
285
+ </>
286
+ ) : (
287
+ <span
288
+ className="button-3 neutral-button"
289
+ onClick={toggleViewData}
290
+ >
291
+ {'View Data'}
292
+ </span>
293
+ )
294
+ ) : (
295
+ <></>
296
+ )}
297
+ </>
298
+ ) : (
299
+ <></>
300
+ )}
301
+ {submittedQueryInfo ? <QueryInfo {...submittedQueryInfo} /> : <></>}
302
+ <div>
303
+ {/*{query.dialect}*/}
304
+ {query && !viewData ? (
305
+ <SyntaxHighlighter language="sql" style={foundation}>
306
+ {format(query.sql, {
307
+ language: 'spark',
308
+ tabWidth: 2,
309
+ keywordCase: 'upper',
310
+ denseOperators: true,
311
+ logicalOperatorNewline: 'before',
312
+ expressionWidth: 10,
313
+ })}
314
+ </SyntaxHighlighter>
315
+ ) : (
316
+ ''
317
+ )}
318
+ </div>
319
+ {data && viewData ? (
320
+ <div className="table-responsive">
321
+ <table className="card-inner-table table">
322
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
323
+ <tr>
324
+ {data[0].columns.map(columnName => (
325
+ <th key={columnName.name}>{columnName.name}</th>
326
+ ))}
327
+ </tr>
328
+ </thead>
329
+ <tbody>{displayedRows}</tbody>
330
+ </table>
331
+ </div>
332
+ ) : (
333
+ <></>
334
+ )}
335
+ </div>
336
+ </div>
337
+ </div>
338
+ </>
339
+ );
340
+ }
341
+
342
+ SQLBuilderPage.defaultProps = {
343
+ djClient: DataJunctionAPI,
344
+ };
@@ -0,0 +1,5 @@
1
+ import { DataJunctionAPI } from '../services/DJService';
2
+ import * as React from 'react';
3
+
4
+ const DJClientContext = React.createContext({ DataJunctionAPI });
5
+ export default DJClientContext;
@@ -1,10 +1,17 @@
1
1
  import { MarkerType } from 'reactflow';
2
2
 
3
- const DJ_URL = 'http://localhost:8000'; //process.env.REACT_APP_DJ_URL;
3
+ const DJ_URL = process.env.REACT_APP_DJ_URL
4
+ ? process.env.REACT_APP_DJ_URL
5
+ : 'http://localhost:8000';
4
6
 
5
7
  export const DataJunctionAPI = {
6
8
  node: async function (name) {
7
9
  const data = await (await fetch(DJ_URL + '/nodes/' + name + '/')).json();
10
+ data.primary_key = data.columns
11
+ .filter(col =>
12
+ col.attributes.some(attr => attr.attribute_type.name === 'primary_key'),
13
+ )
14
+ .map(col => col.name);
8
15
  return data;
9
16
  },
10
17
 
@@ -22,11 +29,64 @@ export const DataJunctionAPI = {
22
29
  return data;
23
30
  },
24
31
 
32
+ node_dag: async function (name) {
33
+ const data = await (
34
+ await fetch(DJ_URL + '/nodes/' + name + '/dag/')
35
+ ).json();
36
+ return data;
37
+ },
38
+
25
39
  metric: async function (name) {
26
40
  const data = await (await fetch(DJ_URL + '/metrics/' + name + '/')).json();
27
41
  return data;
28
42
  },
29
43
 
44
+ clientCode: async function (name) {
45
+ const data = await (
46
+ await fetch(DJ_URL + '/client/python/new_node/' + name)
47
+ ).json();
48
+ return data;
49
+ },
50
+
51
+ cube: async function (name) {
52
+ const data = await (await fetch(DJ_URL + '/cubes/' + name + '/')).json();
53
+ return data;
54
+ },
55
+
56
+ metrics: async function (name) {
57
+ const data = await (await fetch(DJ_URL + '/metrics/')).json();
58
+ return data;
59
+ },
60
+
61
+ commonDimensions: async function (metrics) {
62
+ const metricsQuery = '?' + metrics.map(m => `metric=${m}`).join('&');
63
+ const data = await (
64
+ await fetch(DJ_URL + '/metrics/common/dimensions/' + metricsQuery)
65
+ ).json();
66
+ return data;
67
+ },
68
+
69
+ history: async function (type, name, offset, limit) {
70
+ const data = await (
71
+ await fetch(
72
+ DJ_URL +
73
+ '/history/' +
74
+ type +
75
+ '/' +
76
+ name +
77
+ `/?offset=${offset ? offset : 0}&limit=${limit ? limit : 100}`,
78
+ )
79
+ ).json();
80
+ return data;
81
+ },
82
+
83
+ revisions: async function (name) {
84
+ const data = await (
85
+ await fetch(DJ_URL + '/nodes/' + name + '/revisions/')
86
+ ).json();
87
+ return data;
88
+ },
89
+
30
90
  namespace: async function (nmspce) {
31
91
  const data = await (
32
92
  await fetch(DJ_URL + '/namespaces/' + nmspce + '/')
@@ -39,8 +99,72 @@ export const DataJunctionAPI = {
39
99
  return data;
40
100
  },
41
101
 
102
+ sql: async function (metric_name, selection) {
103
+ const data = await (
104
+ await fetch(
105
+ DJ_URL + '/sql/' + metric_name + '?' + new URLSearchParams(selection),
106
+ )
107
+ ).json();
108
+ return data;
109
+ },
110
+
111
+ materializations: async function (node) {
112
+ const data = await (
113
+ await fetch(DJ_URL + `/nodes/${node}/materializations/`)
114
+ ).json();
115
+
116
+ return await Promise.all(
117
+ data.map(async materialization => {
118
+ materialization.clientCode = await (
119
+ await fetch(
120
+ DJ_URL +
121
+ `/client/python/add_materialization/${node}/${materialization.name}`,
122
+ )
123
+ ).json();
124
+ return materialization;
125
+ }),
126
+ );
127
+ },
128
+
129
+ columns: async function (node) {
130
+ return await Promise.all(
131
+ node.columns.map(async col => {
132
+ col.clientCode = await (
133
+ await fetch(
134
+ DJ_URL +
135
+ `/client/python/link_dimension/${node.name}/${col.name}/${col.dimension?.name}`,
136
+ )
137
+ ).json();
138
+ return col;
139
+ }),
140
+ );
141
+ },
142
+
143
+ sqls: async function (metricSelection, dimensionSelection) {
144
+ const params = new URLSearchParams();
145
+ metricSelection.map(metric => params.append('metrics', metric));
146
+ dimensionSelection.map(dimension => params.append('dimensions', dimension));
147
+ const data = await (await fetch(DJ_URL + '/sql/?' + params)).json();
148
+ return data;
149
+ },
150
+
151
+ data: async function (metricSelection, dimensionSelection) {
152
+ const params = new URLSearchParams();
153
+ metricSelection.map(metric => params.append('metrics', metric));
154
+ dimensionSelection.map(dimension => params.append('dimensions', dimension));
155
+ const data = await (
156
+ await fetch(DJ_URL + '/data/?' + params + '&limit=100&async_=true')
157
+ ).json();
158
+ return data;
159
+ },
160
+
42
161
  lineage: async function (node) {},
43
162
 
163
+ compiledSql: async function (node) {
164
+ const data = await (await fetch(DJ_URL + `/sql/${node}/`)).json();
165
+ return data;
166
+ },
167
+
44
168
  dag: async function (namespace = 'default') {
45
169
  const edges = [];
46
170
  const data = await (await fetch(DJ_URL + '/nodes/')).json();
@@ -46,7 +46,9 @@
46
46
  display: none;
47
47
  }
48
48
 
49
- .dj-node__header {
49
+ .dj-node__header,
50
+ .partition__header,
51
+ .table__header {
50
52
  font-weight: 400;
51
53
  text-transform: uppercase;
52
54
  font-family: 'jetbrains-mono', monospace;
@@ -61,28 +63,84 @@
61
63
  border-color: #c4cbd1;
62
64
  }
63
65
 
64
- .dj-node__body {
66
+ .dj-node__body,
67
+ .partition__body,
68
+ .table__body {
65
69
  background-color: rgb(254, 254, 254);
66
70
  padding: 0.5em 0.7em 0.9em 0.7em;
67
71
  font-family: 'nt-dapper', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
68
72
  Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
69
73
  font-size: 1.7em;
74
+ border-radius: 0 0 8px 8px;
70
75
  }
71
76
 
72
- .dj-node__full {
77
+ .dj-node_highlight {
78
+ border: 10px solid #cccccc !important;
79
+ }
80
+
81
+ .react-flow__node {
82
+ z-index: -1 !important;
83
+ }
84
+
85
+ .react-flow__node-DJNode,
86
+ .dj-node__full,
87
+ .partition__full,
88
+ .table__full {
73
89
  display: flex;
74
90
  flex-direction: column;
75
- height: 100%;
76
91
  border-width: 1px;
77
92
  border-style: solid;
78
93
  border-image: initial;
79
94
  border-color: #b0b9c2;
80
95
  border-radius: 8px;
81
- overflow: hidden;
82
96
  box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px,
83
97
  rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
84
98
  }
85
99
 
100
+ .partition__header,
101
+ .partition__body,
102
+ .partition__full,
103
+ .table__body,
104
+ .table__header {
105
+ font-size: 1em;
106
+ font-family: 'nt-dapper', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
107
+ Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
108
+ }
109
+
110
+ .partition__full,
111
+ .table__full {
112
+ margin-bottom: 1rem;
113
+ border-color: #e0e0e0;
114
+ }
115
+
116
+ .partition__header,
117
+ .table__header {
118
+ background-color: #ccefff50;
119
+ color: #6daaa7;
120
+ text-transform: none;
121
+ border-bottom-style: none;
122
+ }
123
+
124
+ .partition_value {
125
+ background-color: #ccefff50;
126
+ color: #6daaa7;
127
+ border: 1px solid #6daaa750;
128
+ }
129
+
130
+ .table__header {
131
+ background-color: rgba(227, 255, 204, 0.31);
132
+ color: #0aa400;
133
+ }
134
+
135
+ .table__body {
136
+ padding: 16px;
137
+ }
138
+
139
+ .table__full {
140
+ /*border-radius: 0.2em;*/
141
+ /*border: none;*/
142
+ }
143
+
86
144
  .dj-node__metadata {
87
145
  padding: 1em;
88
146
  font-family: 'nt-dapper', -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
@@ -116,3 +174,51 @@
116
174
  font-family: 'jetbrains-mono', monospace;
117
175
  text-transform: lowercase;
118
176
  }
177
+
178
+ .react-flow__handle-left {
179
+ transform: translate(-180%, -50%);
180
+ }
181
+
182
+ .custom-node-subheader {
183
+ --bs-badge-padding-x: 0.4em;
184
+ --bs-badge-padding-y: 0.3em;
185
+ --bs-badge-font-size: 18px;
186
+ --bs-badge-font-weight: 400;
187
+ --bs-badge-color: #fff;
188
+ --bs-badge-border-radius: 0.375rem;
189
+ display: flex;
190
+ flex-direction: column;
191
+ flex-grow: 1;
192
+ padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x);
193
+ margin: 0.55rem;
194
+ font-size: var(--bs-badge-font-size);
195
+ font-weight: var(--bs-badge-font-weight);
196
+ line-height: 1;
197
+ color: var(--bs-badge-color);
198
+ text-align: center;
199
+ white-space: nowrap;
200
+ vertical-align: baseline;
201
+ border-radius: var(--bs-badge-border-radius);
202
+ position: relative;
203
+ justify-content: flex-start;
204
+ }
205
+ .custom-node-port {
206
+ color: #636776;
207
+ font-size: 20px;
208
+ text-align: center;
209
+ }
210
+
211
+ .white_badge {
212
+ background-color: #ffffff !important;
213
+ display: inline-block;
214
+ margin: 0.3rem 0.3rem 0.3rem 0.3rem;
215
+ margin-top: 0.7rem;
216
+ color: #636776;
217
+ }
218
+
219
+ .dimension_attributes {
220
+ max-width: 500px;
221
+ display: inline-flex;
222
+ flex-wrap: wrap;
223
+ justify-content: center;
224
+ }