datajunction-ui 0.0.1-a45 → 0.0.1-a45.dev14
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.
- package/package.json +3 -1
- package/src/app/components/NodeListActions.jsx +69 -0
- package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
- package/src/app/icons/CommitIcon.jsx +45 -0
- package/src/app/icons/DiffIcon.jsx +63 -0
- package/src/app/index.tsx +5 -0
- package/src/app/pages/AddEditNodePage/QueryTesterSection.jsx +62 -0
- package/src/app/pages/NamespacePage/index.jsx +2 -6
- package/src/app/pages/NodePage/NodeHistory.jsx +149 -171
- package/src/app/pages/NodePage/NodeInfoTab.jsx +33 -0
- package/src/app/pages/NodePage/RevisionDiff.jsx +200 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +3 -8
- package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +0 -100
- package/src/app/pages/NodePage/index.jsx +1 -0
- package/src/app/pages/QueryRunner.jsx +31 -0
- package/src/app/services/__tests__/DJService.test.jsx +67 -0
- package/src/index.tsx +1 -0
- package/src/mocks/mockNodes.jsx +48 -0
- package/src/styles/index.css +89 -0
- package/src/styles/node-creation.scss +9 -0
- package/src/app/components/DeleteNode.jsx +0 -55
- package/src/app/components/__tests__/DeleteNode.test.jsx +0 -53
|
@@ -1,223 +1,201 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
-
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
3
|
-
import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
|
|
4
2
|
import * as React from 'react';
|
|
3
|
+
import DiffIcon from '../../icons/DiffIcon';
|
|
4
|
+
import { labelize } from '../../../utils/form';
|
|
5
|
+
import CommitIcon from '../../icons/CommitIcon';
|
|
5
6
|
|
|
6
7
|
export default function NodeHistory({ node, djClient }) {
|
|
7
8
|
const [history, setHistory] = useState([]);
|
|
8
|
-
const [revisions, setRevisions] = useState([]);
|
|
9
9
|
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
const fetchData = async () => {
|
|
12
12
|
if (node) {
|
|
13
13
|
const data = await djClient.history('node', node.name);
|
|
14
|
-
const revisions = await djClient.revisions(node.name);
|
|
15
14
|
setHistory(data);
|
|
16
|
-
setRevisions(revisions);
|
|
17
15
|
}
|
|
18
16
|
};
|
|
19
17
|
fetchData().catch(console.error);
|
|
20
18
|
}, [djClient, node]);
|
|
21
19
|
|
|
22
20
|
const eventData = event => {
|
|
21
|
+
const standard = (
|
|
22
|
+
<>
|
|
23
|
+
<a href={'#'} className={'highlight-svg'} title="Browse Details">
|
|
24
|
+
<CommitIcon /> Details
|
|
25
|
+
</a>
|
|
26
|
+
</>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (event.activity_type === 'update' && event.entity_type === 'node') {
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<a href={`/nodes/${event.node}/revisions/${event.details.version}`}>
|
|
33
|
+
<span className={`badge version`}>{event.details.version}</span>
|
|
34
|
+
</a>
|
|
35
|
+
<a
|
|
36
|
+
href={`/nodes/${event.node}/revisions/${event.details.version}`}
|
|
37
|
+
className={'highlight-svg'}
|
|
38
|
+
title="View Diff"
|
|
39
|
+
>
|
|
40
|
+
<DiffIcon /> Diff
|
|
41
|
+
</a>
|
|
42
|
+
</>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return '';
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const eventDescription = event => {
|
|
49
|
+
if (event.activity_type === 'create' && event.entity_type === 'node') {
|
|
50
|
+
return (
|
|
51
|
+
<div className="history-left">
|
|
52
|
+
<b style={{ textTransform: 'capitalize' }}>{event.activity_type}</b>{' '}
|
|
53
|
+
{event.entity_type}{' '}
|
|
54
|
+
<b>
|
|
55
|
+
<a href={'/nodes/' + event.entity_name}>{event.entity_name}</a>
|
|
56
|
+
</b>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (event.activity_type === 'create' && event.entity_type === 'link') {
|
|
61
|
+
return (
|
|
62
|
+
<div className="history-left">
|
|
63
|
+
<b style={{ textTransform: 'capitalize' }}>{event.activity_type}</b>{' '}
|
|
64
|
+
{event.entity_type} from{' '}
|
|
65
|
+
<b>
|
|
66
|
+
<a href={'/nodes/' + event.entity_name}>{event.entity_name}</a>
|
|
67
|
+
</b>{' '}
|
|
68
|
+
to{' '}
|
|
69
|
+
<b>
|
|
70
|
+
<a href={'/nodes/' + event.details.dimension}>
|
|
71
|
+
{event.details.dimension}
|
|
72
|
+
</a>
|
|
73
|
+
</b>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (event.activity_type === 'update' && event.entity_type === 'node') {
|
|
78
|
+
return (
|
|
79
|
+
<div className="history-left">
|
|
80
|
+
<b style={{ textTransform: 'capitalize' }}>{event.activity_type}</b>{' '}
|
|
81
|
+
{event.entity_type}{' '}
|
|
82
|
+
<b>
|
|
83
|
+
<a href={'/nodes/' + event.entity_name}>{event.entity_name}</a>
|
|
84
|
+
</b>
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (event.activity_type === 'tag' && event.entity_type === 'node') {
|
|
89
|
+
return (
|
|
90
|
+
<div className="history-left">
|
|
91
|
+
Add tag{event.details.tags.length > 1 ? 's' : ''}{' '}
|
|
92
|
+
{event.details.tags.map(tag => (
|
|
93
|
+
<span className={'badge version'}>
|
|
94
|
+
<a href={`/tags/${tag}`}>{tag}</a>
|
|
95
|
+
</span>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (event.activity_type === 'create' && event.entity_type === 'partition') {
|
|
101
|
+
return (
|
|
102
|
+
<div className="history-left">
|
|
103
|
+
Set <b>{event.details.partition.type_} partition</b> on{' '}
|
|
104
|
+
<a href={'/nodes/' + event.node}>{event.details.column}</a>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
23
109
|
if (
|
|
24
110
|
event.activity_type === 'set_attribute' &&
|
|
25
111
|
event.entity_type === 'column_attribute'
|
|
26
112
|
) {
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Set{' '}
|
|
36
|
-
<span className={`badge partition_value`}>
|
|
37
|
-
{event.details.column}
|
|
38
|
-
</span>{' '}
|
|
39
|
-
as{' '}
|
|
40
|
-
<span className={`badge partition_value_highlight`}>
|
|
41
|
-
{attr.name}
|
|
42
|
-
</span>
|
|
43
|
-
</div>
|
|
44
|
-
))
|
|
45
|
-
.reduce((prev, curr) => [prev, <br />, curr], []);
|
|
113
|
+
return (
|
|
114
|
+
<div className="history-left">
|
|
115
|
+
<b>Set column attributes</b> on{' '}
|
|
116
|
+
<b>
|
|
117
|
+
<a href={'/nodes/' + event.node}>{event.node}</a>
|
|
118
|
+
</b>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
46
121
|
}
|
|
47
|
-
|
|
122
|
+
|
|
123
|
+
if (event.entity_type === 'materialization') {
|
|
48
124
|
return (
|
|
49
|
-
<div
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
aria-label="HistoryCreateLink"
|
|
53
|
-
aria-hidden="false"
|
|
54
|
-
>
|
|
55
|
-
Linked{' '}
|
|
56
|
-
<span className={`badge partition_value`}>
|
|
57
|
-
{event.details.column}
|
|
58
|
-
</span>{' '}
|
|
59
|
-
to
|
|
60
|
-
<span className={`badge partition_value_highlight`}>
|
|
61
|
-
{event.details.dimension}
|
|
125
|
+
<div className="history-left">
|
|
126
|
+
<span style={{ textTransform: 'capitalize' }}>
|
|
127
|
+
{event.activity_type}
|
|
62
128
|
</span>{' '}
|
|
63
|
-
|
|
64
|
-
<
|
|
65
|
-
{event.
|
|
66
|
-
</
|
|
129
|
+
<b>{event.entity_type}</b>{' '}
|
|
130
|
+
<a href={`/nodes/${event.node}/materializations`}>
|
|
131
|
+
{event.entity_name}
|
|
132
|
+
</a>
|
|
67
133
|
</div>
|
|
68
134
|
);
|
|
69
135
|
}
|
|
136
|
+
|
|
70
137
|
if (
|
|
71
|
-
event.activity_type === '
|
|
72
|
-
event.entity_type === '
|
|
138
|
+
event.activity_type === 'status_change' &&
|
|
139
|
+
event.entity_type === 'node'
|
|
73
140
|
) {
|
|
74
141
|
return (
|
|
75
|
-
<div
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Initialized materialization{' '}
|
|
82
|
-
<span className={`badge partition_value`}>
|
|
83
|
-
{event.details.materialization}
|
|
84
|
-
</span>
|
|
142
|
+
<div className="history-left">
|
|
143
|
+
<b style={{ textTransform: 'capitalize' }}>
|
|
144
|
+
{labelize(event.activity_type)}
|
|
145
|
+
</b>{' '}
|
|
146
|
+
on <a href={`/nodes/${event.node}`}>{event.node}</a> from{' '}
|
|
147
|
+
<b>{event.pre.status}</b> to <b>{event.post.status}</b>
|
|
85
148
|
</div>
|
|
86
149
|
);
|
|
87
150
|
}
|
|
151
|
+
|
|
88
152
|
if (
|
|
89
153
|
event.activity_type === 'create' &&
|
|
90
154
|
event.entity_type === 'availability'
|
|
91
155
|
) {
|
|
92
156
|
return (
|
|
93
|
-
<div
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
aria-label="HistoryCreateAvailability"
|
|
97
|
-
aria-hidden="false"
|
|
98
|
-
>
|
|
99
|
-
Materialized at{' '}
|
|
100
|
-
<span className={`badge partition_value_highlight`}>
|
|
157
|
+
<div className="history-left">
|
|
158
|
+
Materialized table{' '}
|
|
159
|
+
<code>
|
|
101
160
|
{event.post.catalog}.{event.post.schema_}.{event.post.table}
|
|
102
|
-
</
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
</span>{' '}
|
|
107
|
-
to
|
|
108
|
-
<span className={`badge partition_value`}>
|
|
109
|
-
{event.post.max_temporal_partition}
|
|
110
|
-
</span>
|
|
161
|
+
</code>{' '}
|
|
162
|
+
with partitions between {event.post.min_temporal_partition} and{' '}
|
|
163
|
+
{event.post.max_temporal_partition} available for{' '}
|
|
164
|
+
<a href={'/nodes/' + event.node}>{event.node}</a>.
|
|
111
165
|
</div>
|
|
112
166
|
);
|
|
113
167
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
event.entity_type === 'node'
|
|
117
|
-
) {
|
|
118
|
-
const expr = (
|
|
119
|
-
<div>
|
|
120
|
-
Caused by a change in upstream{' '}
|
|
121
|
-
<a href={`/nodes/${event.details['upstream_node']}`}>
|
|
122
|
-
{event.details['upstream_node']}
|
|
123
|
-
</a>
|
|
124
|
-
</div>
|
|
125
|
-
);
|
|
168
|
+
|
|
169
|
+
if (event.activity_type === 'create' && event.entity_type === 'backfill') {
|
|
126
170
|
return (
|
|
127
|
-
<div
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
>
|
|
133
|
-
Status changed from{' '}
|
|
134
|
-
<span className={`status__${event.pre['status']}`}>
|
|
135
|
-
{event.pre['status']}
|
|
136
|
-
</span>{' '}
|
|
137
|
-
to{' '}
|
|
138
|
-
<span className={`status__${event.post['status']}`}>
|
|
139
|
-
{event.post['status']}
|
|
140
|
-
</span>{' '}
|
|
141
|
-
{event.details['upstream_node'] !== undefined ? expr : ''}
|
|
171
|
+
<div className="history-left">
|
|
172
|
+
Backfill created for materialization {event.details.materialization}{' '}
|
|
173
|
+
for partition {event.details.partition.column_name} from{' '}
|
|
174
|
+
{event.details.partition.range[0]} to{' '}
|
|
175
|
+
{event.details.partition.range[1]}
|
|
142
176
|
</div>
|
|
143
177
|
);
|
|
144
178
|
}
|
|
145
|
-
return (
|
|
146
|
-
<div key={event.id}>
|
|
147
|
-
{JSON.stringify(event.details) === '{}'
|
|
148
|
-
? ''
|
|
149
|
-
: JSON.stringify(event.details)}
|
|
150
|
-
</div>
|
|
151
|
-
);
|
|
152
179
|
};
|
|
153
180
|
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
className={`history_type__${event.activity_type} badge node_type`}
|
|
160
|
-
>
|
|
161
|
-
{event.activity_type}
|
|
162
|
-
</span>
|
|
163
|
-
</td>
|
|
164
|
-
<td>{event.entity_type}</td>
|
|
165
|
-
<td>{event.entity_name}</td>
|
|
166
|
-
<td>{event.user ? event.user : 'unknown'}</td>
|
|
167
|
-
<td>{event.created_at}</td>
|
|
168
|
-
<td>{eventData(event)}</td>
|
|
169
|
-
</tr>
|
|
170
|
-
));
|
|
171
|
-
};
|
|
181
|
+
const removeTagNodeEventsWithoutTags = event =>
|
|
182
|
+
event.activity_type !== 'tag' ||
|
|
183
|
+
(event.activity_type === 'tag' &&
|
|
184
|
+
event.entity_type === 'node' &&
|
|
185
|
+
event.details.tags.length > 0);
|
|
172
186
|
|
|
173
|
-
const revisionsTable = revisions => {
|
|
174
|
-
return revisions.map(revision => (
|
|
175
|
-
<tr key={revision.version}>
|
|
176
|
-
<td className="text-start">
|
|
177
|
-
<span className={`badge node_type__source`}>{revision.version}</span>
|
|
178
|
-
</td>
|
|
179
|
-
<td>{revision.display_name}</td>
|
|
180
|
-
<td>{revision.description}</td>
|
|
181
|
-
<td>
|
|
182
|
-
<SyntaxHighlighter
|
|
183
|
-
language="sql"
|
|
184
|
-
style={foundation}
|
|
185
|
-
wrapLongLines={true}
|
|
186
|
-
>
|
|
187
|
-
{revision.query}
|
|
188
|
-
</SyntaxHighlighter>
|
|
189
|
-
</td>
|
|
190
|
-
<td>{revision.tags}</td>
|
|
191
|
-
</tr>
|
|
192
|
-
));
|
|
193
|
-
};
|
|
194
187
|
return (
|
|
195
|
-
<
|
|
196
|
-
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
</table>
|
|
208
|
-
<table className="card-inner-table table" aria-label="Activity">
|
|
209
|
-
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
210
|
-
<tr>
|
|
211
|
-
<th className="text-start">Activity</th>
|
|
212
|
-
<th>Type</th>
|
|
213
|
-
<th>Name</th>
|
|
214
|
-
<th>User</th>
|
|
215
|
-
<th>Timestamp</th>
|
|
216
|
-
<th>Details</th>
|
|
217
|
-
</tr>
|
|
218
|
-
</thead>
|
|
219
|
-
<tbody>{tableData(history)}</tbody>
|
|
220
|
-
</table>
|
|
221
|
-
</div>
|
|
188
|
+
<ul className="history-border" role="list" aria-label="Activity">
|
|
189
|
+
{history.filter(removeTagNodeEventsWithoutTags).map(event => (
|
|
190
|
+
<li key={`history-row-${event.id}`} className="history">
|
|
191
|
+
{eventDescription(event)}
|
|
192
|
+
<div className={'history-small'}>
|
|
193
|
+
done by <a href="#">{event.user ? event.user : 'unknown'}</a> on{' '}
|
|
194
|
+
{new Date(Date.parse(event.created_at)).toLocaleString()}
|
|
195
|
+
</div>
|
|
196
|
+
<div className="history-right">{eventData(event)}</div>
|
|
197
|
+
</li>
|
|
198
|
+
))}
|
|
199
|
+
</ul>
|
|
222
200
|
);
|
|
223
201
|
}
|
|
@@ -7,6 +7,9 @@ import ListGroupItem from '../../components/ListGroupItem';
|
|
|
7
7
|
import ToggleSwitch from '../../components/ToggleSwitch';
|
|
8
8
|
import DJClientContext from '../../providers/djclient';
|
|
9
9
|
import { labelize } from '../../../utils/form';
|
|
10
|
+
import { AlertMessage } from '../AddEditNodePage/AlertMessage';
|
|
11
|
+
import AlertIcon from '../../icons/AlertIcon';
|
|
12
|
+
import InvalidIcon from '../../icons/InvalidIcon';
|
|
10
13
|
|
|
11
14
|
SyntaxHighlighter.registerLanguage('sql', sql);
|
|
12
15
|
foundation.hljs['padding'] = '2rem';
|
|
@@ -39,6 +42,35 @@ export default function NodeInfoTab({ node }) {
|
|
|
39
42
|
function toggle(value) {
|
|
40
43
|
return !value;
|
|
41
44
|
}
|
|
45
|
+
const metricsWarning =
|
|
46
|
+
node?.type === 'metric' && node?.incompatible_druid_functions.length > 0 ? (
|
|
47
|
+
<div className="message warning" style={{ marginTop: '0.7rem' }}>
|
|
48
|
+
⚠{' '}
|
|
49
|
+
<small>
|
|
50
|
+
The following functions used in the metric definition may not be
|
|
51
|
+
compatible with Druid SQL:{' '}
|
|
52
|
+
{node?.incompatible_druid_functions.map(func => (
|
|
53
|
+
<li style={{ listStyleType: 'none', margin: '0.7rem 0.7rem' }}>
|
|
54
|
+
⇢{' '}
|
|
55
|
+
<span style={{ background: '#fff', padding: '0.3rem' }}>
|
|
56
|
+
{func}
|
|
57
|
+
</span>
|
|
58
|
+
</li>
|
|
59
|
+
))}
|
|
60
|
+
If you need your metrics to be compatible with Druid, please use{' '}
|
|
61
|
+
<a
|
|
62
|
+
href={
|
|
63
|
+
'https://druid.apache.org/docs/latest/querying/sql-functions/'
|
|
64
|
+
}
|
|
65
|
+
>
|
|
66
|
+
these supported functions
|
|
67
|
+
</a>
|
|
68
|
+
.
|
|
69
|
+
</small>
|
|
70
|
+
</div>
|
|
71
|
+
) : (
|
|
72
|
+
''
|
|
73
|
+
);
|
|
42
74
|
const metricQueryDiv = (
|
|
43
75
|
<div className="list-group-item d-flex">
|
|
44
76
|
<div className="gap-2 w-100 justify-content-between py-3">
|
|
@@ -205,6 +237,7 @@ export default function NodeInfoTab({ node }) {
|
|
|
205
237
|
className="list-group align-items-center justify-content-between flex-md-row gap-2"
|
|
206
238
|
style={{ minWidth: '700px' }}
|
|
207
239
|
>
|
|
240
|
+
{node?.type === 'metric' ? metricsWarning : ''}
|
|
208
241
|
<ListGroupItem label="Description" value={node?.description} />
|
|
209
242
|
<div className="list-group-item d-flex">
|
|
210
243
|
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { diffLines, formatLines } from 'unidiff';
|
|
4
|
+
import { parseDiff, Diff, Hunk } from 'react-diff-view';
|
|
5
|
+
|
|
6
|
+
import { useParams } from 'react-router-dom';
|
|
7
|
+
import DJClientContext from '../../providers/djclient';
|
|
8
|
+
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
9
|
+
import { labelize } from '../../../utils/form';
|
|
10
|
+
import DiffIcon from '../../icons/DiffIcon';
|
|
11
|
+
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
12
|
+
import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
|
|
13
|
+
import sql from 'react-syntax-highlighter/dist/cjs/languages/hljs/sql';
|
|
14
|
+
|
|
15
|
+
SyntaxHighlighter.registerLanguage('sql', sql);
|
|
16
|
+
foundation.hljs['padding'] = '2rem';
|
|
17
|
+
|
|
18
|
+
export default function RevisionDiff() {
|
|
19
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
20
|
+
const [revisions, setRevisions] = useState([]);
|
|
21
|
+
|
|
22
|
+
const { name, revision } = useParams();
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
const fetchData = async () => {
|
|
26
|
+
const revisions = await djClient.revisions(name);
|
|
27
|
+
setRevisions(revisions);
|
|
28
|
+
};
|
|
29
|
+
fetchData().catch(console.error);
|
|
30
|
+
}, [djClient, name]);
|
|
31
|
+
|
|
32
|
+
const thisRevision = revisions
|
|
33
|
+
.map((rev, idx) => [idx, rev])
|
|
34
|
+
.filter((rev, idx) => {
|
|
35
|
+
return rev[1].version === revision;
|
|
36
|
+
});
|
|
37
|
+
const prevRevision = revisions.filter(
|
|
38
|
+
(rev, idx) => idx + 1 === thisRevision[0][0],
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const EMPTY_HUNKS = [];
|
|
42
|
+
|
|
43
|
+
const revisionDiff = (older, newer) => {
|
|
44
|
+
const diffObj = {};
|
|
45
|
+
const fields = [
|
|
46
|
+
'display_name',
|
|
47
|
+
'version',
|
|
48
|
+
'query',
|
|
49
|
+
'mode',
|
|
50
|
+
'status',
|
|
51
|
+
'description',
|
|
52
|
+
'columns',
|
|
53
|
+
// 'catalog',
|
|
54
|
+
'schema',
|
|
55
|
+
'table',
|
|
56
|
+
'updated_at',
|
|
57
|
+
];
|
|
58
|
+
if (older) {
|
|
59
|
+
for (const key of fields) {
|
|
60
|
+
if (older[key] && (key !== 'columns' || older.type === 'cube')) {
|
|
61
|
+
diffObj[key] = {};
|
|
62
|
+
diffObj[key].diffText = formatLines(
|
|
63
|
+
diffLines(
|
|
64
|
+
older
|
|
65
|
+
? key === 'columns'
|
|
66
|
+
? older[key].map(col => col.name).join('\n')
|
|
67
|
+
: older[key].toString()
|
|
68
|
+
: '',
|
|
69
|
+
newer
|
|
70
|
+
? key === 'columns'
|
|
71
|
+
? newer[key].map(col => col.name).join('\n')
|
|
72
|
+
: newer[key].toString()
|
|
73
|
+
: '',
|
|
74
|
+
),
|
|
75
|
+
{
|
|
76
|
+
context: 5000,
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
const [diff] = parseDiff(diffObj[key].diffText, {
|
|
80
|
+
nearbySequences: 'zip',
|
|
81
|
+
});
|
|
82
|
+
diffObj[key].diff = diff;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return diffObj;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const diffObjects = revisionDiff(
|
|
90
|
+
prevRevision[0],
|
|
91
|
+
thisRevision[0] ? thisRevision[0][1] : thisRevision[0],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="node__header">
|
|
96
|
+
<NamespaceHeader
|
|
97
|
+
namespace={(prevRevision[0] ? prevRevision[0].name : '')
|
|
98
|
+
.split('.')
|
|
99
|
+
.slice(0, -1)
|
|
100
|
+
.join('.')}
|
|
101
|
+
/>
|
|
102
|
+
<div className="card">
|
|
103
|
+
<div className="card-header">
|
|
104
|
+
<h3
|
|
105
|
+
className="card-title align-items-start flex-column"
|
|
106
|
+
style={{ display: 'inline-block' }}
|
|
107
|
+
>
|
|
108
|
+
<span
|
|
109
|
+
className="card-label fw-bold text-gray-800"
|
|
110
|
+
role="dialog"
|
|
111
|
+
aria-hidden="false"
|
|
112
|
+
aria-label="DisplayName"
|
|
113
|
+
>
|
|
114
|
+
<a
|
|
115
|
+
href={'/nodes/' + prevRevision[0]?.name}
|
|
116
|
+
className=""
|
|
117
|
+
role="dialog"
|
|
118
|
+
aria-hidden="false"
|
|
119
|
+
aria-label="NodeName"
|
|
120
|
+
>
|
|
121
|
+
{prevRevision[0]?.name}
|
|
122
|
+
</a>{' '}
|
|
123
|
+
<span
|
|
124
|
+
className={
|
|
125
|
+
'node_type__' + prevRevision[0]?.type + ' badge node_type'
|
|
126
|
+
}
|
|
127
|
+
role="dialog"
|
|
128
|
+
aria-hidden="false"
|
|
129
|
+
aria-label="NodeType"
|
|
130
|
+
>
|
|
131
|
+
{prevRevision[0]?.type}
|
|
132
|
+
</span>
|
|
133
|
+
</span>
|
|
134
|
+
</h3>
|
|
135
|
+
<div>
|
|
136
|
+
<span
|
|
137
|
+
className="rounded-pill badge version"
|
|
138
|
+
style={{ marginLeft: '0.5rem', fontSize: '16px' }}
|
|
139
|
+
>
|
|
140
|
+
{prevRevision[0]?.version}
|
|
141
|
+
</span>
|
|
142
|
+
<DiffIcon />
|
|
143
|
+
<span
|
|
144
|
+
className="rounded-pill badge version"
|
|
145
|
+
style={{ marginLeft: '0.3rem', fontSize: '16px' }}
|
|
146
|
+
>
|
|
147
|
+
{thisRevision[0] ? thisRevision[0][1].version : ''}
|
|
148
|
+
</span>{' '}
|
|
149
|
+
</div>
|
|
150
|
+
{Object.keys(diffObjects).map(field => {
|
|
151
|
+
return (
|
|
152
|
+
<div className="diff" aria-label={'DiffView'} role={'gridcell'}>
|
|
153
|
+
<h4>
|
|
154
|
+
{labelize(field)}{' '}
|
|
155
|
+
<small className="no-change-banner">
|
|
156
|
+
{diffObjects[field]?.diff.hunks.length > 0
|
|
157
|
+
? ''
|
|
158
|
+
: 'no change'}
|
|
159
|
+
</small>
|
|
160
|
+
</h4>
|
|
161
|
+
{diffObjects[field]?.diff.hunks.length > 0 ? (
|
|
162
|
+
<Diff
|
|
163
|
+
viewType="split"
|
|
164
|
+
diffType=""
|
|
165
|
+
hunks={diffObjects[field]?.diff.hunks || EMPTY_HUNKS}
|
|
166
|
+
tokens={diffObjects[field]?.tokens}
|
|
167
|
+
>
|
|
168
|
+
{hunks =>
|
|
169
|
+
hunks.map(hunk => <Hunk key={hunk.content} hunk={hunk} />)
|
|
170
|
+
}
|
|
171
|
+
</Diff>
|
|
172
|
+
) : (
|
|
173
|
+
<div className="no-change">
|
|
174
|
+
{prevRevision[0] ? (
|
|
175
|
+
field === 'query' ? (
|
|
176
|
+
<>
|
|
177
|
+
<SyntaxHighlighter
|
|
178
|
+
language="sql"
|
|
179
|
+
style={foundation}
|
|
180
|
+
wrapLongLines={true}
|
|
181
|
+
>
|
|
182
|
+
{prevRevision[0].query}
|
|
183
|
+
</SyntaxHighlighter>
|
|
184
|
+
</>
|
|
185
|
+
) : (
|
|
186
|
+
prevRevision[0][field].toString()
|
|
187
|
+
)
|
|
188
|
+
) : (
|
|
189
|
+
''
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
</div>
|
|
194
|
+
);
|
|
195
|
+
})}
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -88,6 +88,7 @@ describe('<NodePage />', () => {
|
|
|
88
88
|
created_at: '2023-08-21T16:48:56.932162+00:00',
|
|
89
89
|
tags: [{ name: 'purpose', display_name: 'Purpose' }],
|
|
90
90
|
primary_key: [],
|
|
91
|
+
incompatible_druid_functions: ['IF'],
|
|
91
92
|
createNodeClientCode:
|
|
92
93
|
'dj = DJBuilder(DJ_URL)\n\navg_repair_price = dj.create_metric(\n description="Average repair price",\n display_name="Default: Avg Repair Price",\n name="default.avg_repair_price",\n primary_key=[],\n query="""SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n"""\n)',
|
|
93
94
|
dimensions: [
|
|
@@ -501,15 +502,9 @@ describe('<NodePage />', () => {
|
|
|
501
502
|
'node',
|
|
502
503
|
mocks.mockMetricNode.name,
|
|
503
504
|
);
|
|
504
|
-
expect(djClient.DataJunctionAPI.revisions).toHaveBeenCalledWith(
|
|
505
|
-
mocks.mockMetricNode.name,
|
|
506
|
-
);
|
|
507
505
|
expect(
|
|
508
|
-
screen.getByRole('
|
|
509
|
-
).
|
|
510
|
-
expect(screen.getByRole('table', { name: 'Activity' })).toHaveTextContent(
|
|
511
|
-
'ActivityTypeNameUserTimestampDetailscreatenodedefault.avg_repair_priceunknown2023-08-21T16:48:56.950482+00:00',
|
|
512
|
-
);
|
|
506
|
+
screen.getByRole('list', { name: 'Activity' }),
|
|
507
|
+
).toBeInTheDocument();
|
|
513
508
|
screen
|
|
514
509
|
.queryAllByRole('cell', {
|
|
515
510
|
name: 'HistoryAttribute',
|