datajunction-ui 0.0.1-rc.19 → 0.0.1-rc.20

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 (66) hide show
  1. package/package.json +6 -5
  2. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  3. package/src/app/components/DeleteNode.jsx +79 -0
  4. package/src/app/components/ListGroupItem.jsx +8 -1
  5. package/src/app/components/QueryInfo.jsx +4 -4
  6. package/src/app/components/Tab.jsx +9 -1
  7. package/src/app/components/ToggleSwitch.jsx +3 -0
  8. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  9. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  10. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  11. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +3 -0
  12. package/src/app/components/djgraph/DJNodeColumns.jsx +4 -1
  13. package/src/app/components/djgraph/DJNodeDimensions.jsx +9 -2
  14. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  15. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  16. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  17. package/src/app/index.tsx +6 -0
  18. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +15 -2
  19. package/src/app/pages/AddEditNodePage/FullNameField.jsx +2 -1
  20. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +77 -0
  21. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +93 -0
  22. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +34 -3
  23. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +4 -2
  24. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +53 -0
  25. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +53 -0
  26. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +0 -81
  27. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +82 -257
  28. package/src/app/pages/AddEditNodePage/index.jsx +13 -5
  29. package/src/app/pages/LoginPage/__tests__/index.test.jsx +70 -0
  30. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +95 -0
  31. package/src/app/pages/NamespacePage/index.jsx +8 -5
  32. package/src/app/pages/NodePage/ClientCodePopover.jsx +3 -1
  33. package/src/app/pages/NodePage/EditColumnPopover.jsx +102 -0
  34. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +135 -0
  35. package/src/app/pages/NodePage/NodeColumnTab.jsx +80 -17
  36. package/src/app/pages/NodePage/NodeHistory.jsx +63 -30
  37. package/src/app/pages/NodePage/NodeInfoTab.jsx +52 -7
  38. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +50 -27
  39. package/src/app/pages/NodePage/NodeSQLTab.jsx +0 -10
  40. package/src/app/pages/NodePage/NodesWithDimension.jsx +4 -2
  41. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
  42. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  43. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
  44. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
  45. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
  46. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +725 -0
  47. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  48. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +402 -0
  49. package/src/app/pages/NodePage/index.jsx +22 -6
  50. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  51. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  52. package/src/app/pages/RegisterTablePage/index.jsx +163 -0
  53. package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
  54. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  55. package/src/app/pages/SQLBuilderPage/index.jsx +61 -43
  56. package/src/app/services/DJService.js +125 -54
  57. package/src/app/services/__tests__/DJService.test.jsx +609 -0
  58. package/src/mocks/mockNodes.jsx +1397 -0
  59. package/src/setupTests.ts +30 -0
  60. package/src/styles/index.css +43 -0
  61. package/src/styles/node-creation.scss +7 -0
  62. package/src/utils/form.jsx +23 -0
  63. package/.github/pull_request_template.md +0 -11
  64. package/.github/workflows/ci.yml +0 -33
  65. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -118
  66. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,102 @@
1
+ import { useContext, useState } from 'react';
2
+ import * as React from 'react';
3
+ import DJClientContext from '../../providers/djclient';
4
+ import { Form, Formik } from 'formik';
5
+ import { FormikSelect } from '../AddEditNodePage/FormikSelect';
6
+ import EditIcon from '../../icons/EditIcon';
7
+ import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
8
+
9
+ export default function EditColumnPopover({ column, node, options, onSubmit }) {
10
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
11
+ const [popoverAnchor, setPopoverAnchor] = useState(false);
12
+
13
+ const saveAttributes = async (
14
+ { node, column, attributes },
15
+ { setSubmitting, setStatus },
16
+ ) => {
17
+ setSubmitting(false);
18
+ const response = await djClient.setAttributes(node, column, attributes);
19
+ if (response.status === 200 || response.status === 201) {
20
+ setStatus({ success: 'Saved!' });
21
+ } else {
22
+ setStatus({
23
+ failure: `${response.json.message}`,
24
+ });
25
+ }
26
+ onSubmit();
27
+ // window.location.reload();
28
+ };
29
+
30
+ return (
31
+ <>
32
+ <button
33
+ className="edit_button"
34
+ aria-label="EditColumn"
35
+ tabIndex="0"
36
+ onClick={() => {
37
+ setPopoverAnchor(!popoverAnchor);
38
+ }}
39
+ >
40
+ <EditIcon />
41
+ </button>
42
+ <div
43
+ className="popover"
44
+ role="dialog"
45
+ aria-label="client-code"
46
+ style={{ display: popoverAnchor === false ? 'none' : 'block' }}
47
+ >
48
+ <Formik
49
+ initialValues={{
50
+ column: column.name,
51
+ node: node.name,
52
+ attributes: [],
53
+ }}
54
+ onSubmit={saveAttributes}
55
+ >
56
+ {function Render({ isSubmitting, status, setFieldValue }) {
57
+ return (
58
+ <Form>
59
+ {displayMessageAfterSubmit(status)}
60
+ <span data-testid="edit-attributes">
61
+ <FormikSelect
62
+ selectOptions={options}
63
+ formikFieldName="attributes"
64
+ placeholder="Select column attributes"
65
+ className=""
66
+ defaultValue={column.attributes.map(attr => {
67
+ return {
68
+ value: attr.attribute_type.name,
69
+ label: labelize(attr.attribute_type.name),
70
+ };
71
+ })}
72
+ isMulti={true}
73
+ />
74
+ </span>
75
+ <input
76
+ hidden={true}
77
+ name="column"
78
+ value={column.name}
79
+ readOnly={true}
80
+ />
81
+ <input
82
+ hidden={true}
83
+ name="node"
84
+ value={node.name}
85
+ readOnly={true}
86
+ />
87
+ <button
88
+ className="add_node"
89
+ type="submit"
90
+ aria-label="SaveEditColumn"
91
+ aria-hidden="false"
92
+ >
93
+ Save
94
+ </button>
95
+ </Form>
96
+ );
97
+ }}
98
+ </Formik>
99
+ </div>
100
+ </>
101
+ );
102
+ }
@@ -0,0 +1,135 @@
1
+ import { useContext, useState } from 'react';
2
+ import * as React from 'react';
3
+ import DJClientContext from '../../providers/djclient';
4
+ import { Form, Formik } from 'formik';
5
+ import { FormikSelect } from '../AddEditNodePage/FormikSelect';
6
+ import EditIcon from '../../icons/EditIcon';
7
+ import { displayMessageAfterSubmit } from '../../../utils/form';
8
+
9
+ export default function LinkDimensionPopover({
10
+ column,
11
+ node,
12
+ options,
13
+ onSubmit,
14
+ }) {
15
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
16
+ const [popoverAnchor, setPopoverAnchor] = useState(false);
17
+ const columnDimension = column.dimension;
18
+
19
+ const handleSubmit = async (
20
+ { node, column, dimension },
21
+ { setSubmitting, setStatus },
22
+ ) => {
23
+ setSubmitting(false);
24
+ console.log('dimension', dimension, 'columnDimension', columnDimension);
25
+ if (columnDimension?.name && dimension === 'Remove') {
26
+ await unlinkDimension(node, column, columnDimension?.name, setStatus);
27
+ } else {
28
+ await linkDimension(node, column, dimension, setStatus);
29
+ }
30
+ onSubmit();
31
+ };
32
+
33
+ const linkDimension = async (node, column, dimension, setStatus) => {
34
+ const response = await djClient.linkDimension(node, column, dimension);
35
+ if (response.status === 200 || response.status === 201) {
36
+ setStatus({ success: 'Saved!' });
37
+ } else {
38
+ setStatus({
39
+ failure: `${response.json.message}`,
40
+ });
41
+ }
42
+ };
43
+
44
+ const unlinkDimension = async (node, column, currentDimension, setStatus) => {
45
+ const response = await djClient.unlinkDimension(
46
+ node,
47
+ column,
48
+ currentDimension,
49
+ );
50
+ if (response.status === 200 || response.status === 201) {
51
+ setStatus({ success: 'Removed dimension link!' });
52
+ } else {
53
+ setStatus({
54
+ failure: `${response.json.message}`,
55
+ });
56
+ }
57
+ };
58
+
59
+ return (
60
+ <>
61
+ <button
62
+ className="edit_button"
63
+ aria-label="LinkDimension"
64
+ tabIndex="0"
65
+ onClick={() => {
66
+ setPopoverAnchor(!popoverAnchor);
67
+ }}
68
+ >
69
+ <EditIcon />
70
+ </button>
71
+ <div
72
+ className="popover"
73
+ role="dialog"
74
+ aria-label="client-code"
75
+ style={{ display: popoverAnchor === false ? 'none' : 'block' }}
76
+ >
77
+ <Formik
78
+ initialValues={{
79
+ column: column.name,
80
+ node: node.name,
81
+ dimension: '',
82
+ currentDimension: column.dimension?.name,
83
+ }}
84
+ onSubmit={handleSubmit}
85
+ >
86
+ {function Render({ isSubmitting, status, setFieldValue }) {
87
+ return (
88
+ <Form>
89
+ {displayMessageAfterSubmit(status)}
90
+ <span data-testid="link-dimension">
91
+ <FormikSelect
92
+ selectOptions={[
93
+ { value: 'Remove', label: '[Remove Dimension]' },
94
+ ].concat(options)}
95
+ formikFieldName="dimension"
96
+ placeholder="Select dimension to link"
97
+ className=""
98
+ defaultValue={
99
+ column.dimension
100
+ ? {
101
+ value: column.dimension.name,
102
+ label: column.dimension.name,
103
+ }
104
+ : ''
105
+ }
106
+ />
107
+ </span>
108
+ <input
109
+ hidden={true}
110
+ name="column"
111
+ value={column.name}
112
+ readOnly={true}
113
+ />
114
+ <input
115
+ hidden={true}
116
+ name="node"
117
+ value={node.name}
118
+ readOnly={true}
119
+ />
120
+ <button
121
+ className="add_node"
122
+ type="submit"
123
+ aria-label="SaveLinkDimension"
124
+ aria-hidden="false"
125
+ >
126
+ Save
127
+ </button>
128
+ </Form>
129
+ );
130
+ }}
131
+ </Formik>
132
+ </div>
133
+ </>
134
+ );
135
+ }
@@ -1,7 +1,13 @@
1
1
  import { useEffect, useState } from 'react';
2
2
  import ClientCodePopover from './ClientCodePopover';
3
+ import * as React from 'react';
4
+ import EditColumnPopover from './EditColumnPopover';
5
+ import LinkDimensionPopover from './LinkDimensionPopover';
6
+ import { labelize } from '../../../utils/form';
3
7
 
4
8
  export default function NodeColumnTab({ node, djClient }) {
9
+ const [attributes, setAttributes] = useState([]);
10
+ const [dimensions, setDimensions] = useState([]);
5
11
  const [columns, setColumns] = useState([]);
6
12
  useEffect(() => {
7
13
  const fetchData = async () => {
@@ -10,12 +16,57 @@ export default function NodeColumnTab({ node, djClient }) {
10
16
  fetchData().catch(console.error);
11
17
  }, [djClient, node]);
12
18
 
19
+ useEffect(() => {
20
+ const fetchData = async () => {
21
+ const attributes = await djClient.attributes();
22
+ const options = attributes.map(attr => {
23
+ return { value: attr.name, label: labelize(attr.name) };
24
+ });
25
+ setAttributes(options);
26
+ };
27
+ fetchData().catch(console.error);
28
+ }, [djClient]);
29
+
30
+ useEffect(() => {
31
+ const fetchData = async () => {
32
+ const dimensions = await djClient.dimensions();
33
+ const options = dimensions.map(name => {
34
+ return { value: name, label: name };
35
+ });
36
+ setDimensions(options);
37
+ };
38
+ fetchData().catch(console.error);
39
+ }, [djClient]);
40
+
41
+ const showColumnAttributes = col => {
42
+ return col.attributes.map((attr, idx) => (
43
+ <span
44
+ className="node_type__dimension badge node_type"
45
+ key={`col-attr-${col.name}-${idx}`}
46
+ >
47
+ {attr.attribute_type.name.replace(/_/, ' ')}
48
+ </span>
49
+ ));
50
+ };
51
+
13
52
  const columnList = columns => {
14
53
  return columns.map(col => (
15
- <tr>
16
- <td className="text-start">{col.name}</td>
54
+ <tr key={col.name}>
55
+ <td
56
+ className="text-start"
57
+ role="columnheader"
58
+ aria-label="ColumnName"
59
+ aria-hidden="false"
60
+ >
61
+ {col.name}
62
+ </td>
17
63
  <td>
18
- <span className="node_type__transform badge node_type">
64
+ <span
65
+ className="node_type__transform badge node_type"
66
+ role="columnheader"
67
+ aria-label="ColumnType"
68
+ aria-hidden="false"
69
+ >
19
70
  {col.type}
20
71
  </span>
21
72
  </td>
@@ -28,17 +79,27 @@ export default function NodeColumnTab({ node, djClient }) {
28
79
  ) : (
29
80
  ''
30
81
  )}{' '}
82
+ <LinkDimensionPopover
83
+ column={col}
84
+ node={node}
85
+ options={dimensions}
86
+ onSubmit={async () => {
87
+ const res = await djClient.node(node.name);
88
+ setColumns(res.columns);
89
+ }}
90
+ />
31
91
  </td>
32
92
  <td>
33
- {col.attributes.find(
34
- attr => attr.attribute_type.name === 'dimension',
35
- ) ? (
36
- <span className="node_type__dimension badge node_type">
37
- dimensional
38
- </span>
39
- ) : (
40
- ''
41
- )}
93
+ {showColumnAttributes(col)}
94
+ <EditColumnPopover
95
+ column={col}
96
+ node={node}
97
+ options={attributes}
98
+ onSubmit={async () => {
99
+ const res = await djClient.node(node.name);
100
+ setColumns(res.columns);
101
+ }}
102
+ />
42
103
  </td>
43
104
  </tr>
44
105
  ));
@@ -48,12 +109,14 @@ export default function NodeColumnTab({ node, djClient }) {
48
109
  <div className="table-responsive">
49
110
  <table className="card-inner-table table">
50
111
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
51
- <th className="text-start">Column</th>
52
- <th>Type</th>
53
- <th>Dimension</th>
54
- <th>Attributes</th>
112
+ <tr>
113
+ <th className="text-start">Column</th>
114
+ <th>Type</th>
115
+ <th>Linked Dimension</th>
116
+ <th>Attributes</th>
117
+ </tr>
55
118
  </thead>
56
- {columnList(columns)}
119
+ <tbody>{columnList(columns)}</tbody>
57
120
  </table>
58
121
  </div>
59
122
  );
@@ -6,10 +6,12 @@ export default function NodeHistory({ node, djClient }) {
6
6
 
7
7
  useEffect(() => {
8
8
  const fetchData = async () => {
9
- const data = await djClient.history('node', node.name);
10
- setHistory(data);
11
- const revisions = await djClient.revisions(node.name);
12
- setRevisions(revisions);
9
+ if (node) {
10
+ const data = await djClient.history('node', node.name);
11
+ setHistory(data);
12
+ const revisions = await djClient.revisions(node.name);
13
+ setRevisions(revisions);
14
+ }
13
15
  };
14
16
  fetchData().catch(console.error);
15
17
  }, [djClient, node]);
@@ -21,20 +23,32 @@ export default function NodeHistory({ node, djClient }) {
21
23
  ) {
22
24
  return event.details.attributes
23
25
  .map(attr => (
24
- <div>
26
+ <div
27
+ key={event.id}
28
+ role="cell"
29
+ aria-label="HistoryAttribute"
30
+ aria-hidden="false"
31
+ >
25
32
  Set{' '}
26
- <span className={`badge partition_value`}>{attr.column_name}</span>{' '}
33
+ <span className={`badge partition_value`}>
34
+ {event.details.column}
35
+ </span>{' '}
27
36
  as{' '}
28
37
  <span className={`badge partition_value_highlight`}>
29
- {attr.attribute_type_name}
38
+ {attr.name}
30
39
  </span>
31
40
  </div>
32
41
  ))
33
- .reduce((prev, curr) => [prev, <br />, curr]);
42
+ .reduce((prev, curr) => [prev, <br />, curr], []);
34
43
  }
35
44
  if (event.activity_type === 'create' && event.entity_type === 'link') {
36
45
  return (
37
- <div>
46
+ <div
47
+ key={event.id}
48
+ role="cell"
49
+ aria-label="HistoryCreateLink"
50
+ aria-hidden="false"
51
+ >
38
52
  Linked{' '}
39
53
  <span className={`badge partition_value`}>
40
54
  {event.details.column}
@@ -55,7 +69,12 @@ export default function NodeHistory({ node, djClient }) {
55
69
  event.entity_type === 'materialization'
56
70
  ) {
57
71
  return (
58
- <div>
72
+ <div
73
+ key={event.id}
74
+ role="cell"
75
+ aria-label="HistoryCreateMaterialization"
76
+ aria-hidden="false"
77
+ >
59
78
  Initialized materialization{' '}
60
79
  <span className={`badge partition_value`}>
61
80
  {event.details.materialization}
@@ -68,7 +87,12 @@ export default function NodeHistory({ node, djClient }) {
68
87
  event.entity_type === 'availability'
69
88
  ) {
70
89
  return (
71
- <div>
90
+ <div
91
+ key={event.id}
92
+ role="cell"
93
+ aria-label="HistoryCreateAvailability"
94
+ aria-hidden="false"
95
+ >
72
96
  Materialized at{' '}
73
97
  <span className={`badge partition_value_highlight`}>
74
98
  {event.post.catalog}.{event.post.schema_}.{event.post.table}
@@ -97,7 +121,12 @@ export default function NodeHistory({ node, djClient }) {
97
121
  </div>
98
122
  );
99
123
  return (
100
- <div>
124
+ <div
125
+ key={event.id}
126
+ role="cell"
127
+ aria-label="HistoryNodeStatusChange"
128
+ aria-hidden="false"
129
+ >
101
130
  Status changed from{' '}
102
131
  <span className={`status__${event.pre['status']}`}>
103
132
  {event.pre['status']}
@@ -111,7 +140,7 @@ export default function NodeHistory({ node, djClient }) {
111
140
  );
112
141
  }
113
142
  return (
114
- <div>
143
+ <div key={event.id}>
115
144
  {JSON.stringify(event.details) === '{}'
116
145
  ? ''
117
146
  : JSON.stringify(event.details)}
@@ -121,7 +150,7 @@ export default function NodeHistory({ node, djClient }) {
121
150
 
122
151
  const tableData = history => {
123
152
  return history.map(event => (
124
- <tr>
153
+ <tr key={`history-row-${event.id}`}>
125
154
  <td className="text-start">
126
155
  <span
127
156
  className={`history_type__${event.activity_type} badge node_type`}
@@ -140,7 +169,7 @@ export default function NodeHistory({ node, djClient }) {
140
169
 
141
170
  const revisionsTable = revisions => {
142
171
  return revisions.map(revision => (
143
- <tr>
172
+ <tr key={revision.version}>
144
173
  <td className="text-start">
145
174
  <span className={`badge node_type__source`}>{revision.version}</span>
146
175
  </td>
@@ -153,26 +182,30 @@ export default function NodeHistory({ node, djClient }) {
153
182
  };
154
183
  return (
155
184
  <div className="table-vertical">
156
- <table className="card-inner-table table">
185
+ <table className="card-inner-table table" aria-label="Revisions">
157
186
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
158
- <th className="text-start">Version</th>
159
- <th>Display Name</th>
160
- <th>Description</th>
161
- <th>Query</th>
162
- <th>Tags</th>
187
+ <tr>
188
+ <th className="text-start">Version</th>
189
+ <th>Display Name</th>
190
+ <th>Description</th>
191
+ <th>Query</th>
192
+ <th>Tags</th>
193
+ </tr>
163
194
  </thead>
164
- {revisionsTable(revisions)}
195
+ <tbody>{revisionsTable(revisions)}</tbody>
165
196
  </table>
166
- <table className="card-inner-table table">
197
+ <table className="card-inner-table table" aria-label="Activity">
167
198
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
168
- <th className="text-start">Activity</th>
169
- <th>Type</th>
170
- <th>Name</th>
171
- <th>User</th>
172
- <th>Timestamp</th>
173
- <th>Details</th>
199
+ <tr>
200
+ <th className="text-start">Activity</th>
201
+ <th>Type</th>
202
+ <th>Name</th>
203
+ <th>User</th>
204
+ <th>Timestamp</th>
205
+ <th>Details</th>
206
+ </tr>
174
207
  </thead>
175
- {tableData(history)}
208
+ <tbody>{tableData(history)}</tbody>
176
209
  </table>
177
210
  </div>
178
211
  );
@@ -73,7 +73,13 @@ export default function NodeInfoTab({ node }) {
73
73
  <h6 className="mb-0 w-100">Cube Elements</h6>
74
74
  <div className={`list-group-item`}>
75
75
  {node.cube_elements.map(cubeElem => (
76
- <div className="button-3 cube-element">
76
+ <div
77
+ className="button-3 cube-element"
78
+ key={cubeElem.name}
79
+ role="cell"
80
+ aria-label="CubeElement"
81
+ aria-hidden="false"
82
+ >
77
83
  <a href={`/nodes/${cubeElem.node_name}`}>
78
84
  {cubeElem.type === 'metric'
79
85
  ? cubeElem.node_name
@@ -107,6 +113,9 @@ export default function NodeInfoTab({ node }) {
107
113
  <span
108
114
  className="rounded-pill badge bg-secondary-soft"
109
115
  style={{ marginLeft: '0.5rem', fontSize: '100%' }}
116
+ role="dialog"
117
+ aria-hidden="false"
118
+ aria-label="Version"
110
119
  >
111
120
  {node?.version}
112
121
  </span>
@@ -115,7 +124,12 @@ export default function NodeInfoTab({ node }) {
115
124
  {node.type === 'source' ? (
116
125
  <div>
117
126
  <h6 className="mb-0 w-100">Table</h6>
118
- <p className="mb-0 opacity-75">
127
+ <p
128
+ className="mb-0 opacity-75"
129
+ role="dialog"
130
+ aria-hidden="false"
131
+ aria-label="Table"
132
+ >
119
133
  {node?.catalog.name}.{node?.schema_}.{node?.table}
120
134
  </p>
121
135
  </div>
@@ -124,27 +138,58 @@ export default function NodeInfoTab({ node }) {
124
138
  )}
125
139
  <div>
126
140
  <h6 className="mb-0 w-100">Status</h6>
127
- <p className="mb-0 opacity-75">
141
+ <p
142
+ className="mb-0 opacity-75"
143
+ role="dialog"
144
+ aria-hidden="false"
145
+ aria-label="NodeStatus"
146
+ >
128
147
  <NodeStatus node={node} />
129
148
  </p>
130
149
  </div>
131
150
  <div>
132
151
  <h6 className="mb-0 w-100">Mode</h6>
133
152
  <p className="mb-0 opacity-75">
134
- <span className="status">{node?.mode}</span>
153
+ <span
154
+ className="status"
155
+ role="dialog"
156
+ aria-hidden="false"
157
+ aria-label="Mode"
158
+ >
159
+ {node?.mode}
160
+ </span>
135
161
  </p>
136
162
  </div>
137
163
  <div>
138
164
  <h6 className="mb-0 w-100">Tags</h6>
139
- <p className="mb-0 opacity-75">{nodeTags}</p>
165
+ <p
166
+ className="mb-0 opacity-75"
167
+ role="dialog"
168
+ aria-hidden="false"
169
+ aria-label="Tags"
170
+ >
171
+ {nodeTags}
172
+ </p>
140
173
  </div>
141
174
  <div>
142
175
  <h6 className="mb-0 w-100">Primary Key</h6>
143
- <p className="mb-0 opacity-75">{node?.primary_key}</p>
176
+ <p
177
+ className="mb-0 opacity-75"
178
+ role="dialog"
179
+ aria-hidden="false"
180
+ aria-label="PrimaryKey"
181
+ >
182
+ {node?.primary_key?.join(', ')}
183
+ </p>
144
184
  </div>
145
185
  <div>
146
186
  <h6 className="mb-0 w-100">Last Updated</h6>
147
- <p className="mb-0 opacity-75">
187
+ <p
188
+ className="mb-0 opacity-75"
189
+ role="dialog"
190
+ aria-hidden="false"
191
+ aria-label="UpdatedAt"
192
+ >
148
193
  {new Date(node?.updated_at).toDateString()}
149
194
  </p>
150
195
  </div>