datajunction-ui 0.0.1-a1

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 (154) hide show
  1. package/.babel-plugin-macrosrc.js +5 -0
  2. package/.env +3 -0
  3. package/.eslintrc.js +20 -0
  4. package/.gitattributes +201 -0
  5. package/.husky/pre-commit +6 -0
  6. package/.nvmrc +1 -0
  7. package/.prettierignore +6 -0
  8. package/.prettierrc +9 -0
  9. package/.stylelintrc +7 -0
  10. package/LICENSE +22 -0
  11. package/Makefile +3 -0
  12. package/README.md +10 -0
  13. package/dj-logo.svg +10 -0
  14. package/internals/testing/loadable.mock.tsx +6 -0
  15. package/package.json +189 -0
  16. package/public/favicon.ico +0 -0
  17. package/public/index.html +26 -0
  18. package/public/manifest.json +15 -0
  19. package/public/robots.txt +3 -0
  20. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  21. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +9 -0
  22. package/src/app/__tests__/index.test.tsx +14 -0
  23. package/src/app/components/DeleteNode.jsx +55 -0
  24. package/src/app/components/ListGroupItem.jsx +24 -0
  25. package/src/app/components/NamespaceHeader.jsx +31 -0
  26. package/src/app/components/QueryInfo.jsx +77 -0
  27. package/src/app/components/Tab.jsx +25 -0
  28. package/src/app/components/ToggleSwitch.jsx +20 -0
  29. package/src/app/components/__tests__/DeleteNode.test.jsx +53 -0
  30. package/src/app/components/__tests__/ListGroupItem.test.tsx +16 -0
  31. package/src/app/components/__tests__/NamespaceHeader.test.jsx +14 -0
  32. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  33. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  34. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  35. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +29 -0
  36. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +47 -0
  37. package/src/app/components/djgraph/Collapse.jsx +46 -0
  38. package/src/app/components/djgraph/DJNode.jsx +89 -0
  39. package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
  40. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  41. package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
  42. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  43. package/src/app/components/djgraph/__tests__/DJNode.test.tsx +24 -0
  44. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  45. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  46. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +117 -0
  47. package/src/app/constants.js +2 -0
  48. package/src/app/icons/AlertIcon.jsx +32 -0
  49. package/src/app/icons/CollapsedIcon.jsx +15 -0
  50. package/src/app/icons/DJLogo.jsx +36 -0
  51. package/src/app/icons/DeleteIcon.jsx +21 -0
  52. package/src/app/icons/EditIcon.jsx +18 -0
  53. package/src/app/icons/ExpandedIcon.jsx +15 -0
  54. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  55. package/src/app/icons/InvalidIcon.jsx +14 -0
  56. package/src/app/icons/LoadingIcon.jsx +14 -0
  57. package/src/app/icons/PythonIcon.jsx +52 -0
  58. package/src/app/icons/TableIcon.jsx +14 -0
  59. package/src/app/icons/ValidIcon.jsx +14 -0
  60. package/src/app/index.tsx +108 -0
  61. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
  62. package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
  63. package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
  64. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
  65. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +103 -0
  66. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +132 -0
  67. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  68. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  69. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  70. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  71. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  72. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  73. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +180 -0
  74. package/src/app/pages/AddEditNodePage/index.jsx +396 -0
  75. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  76. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  77. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  78. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  79. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  80. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  81. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  82. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  83. package/src/app/pages/LoginPage/index.jsx +17 -0
  84. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  85. package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
  86. package/src/app/pages/NamespacePage/Loadable.jsx +16 -0
  87. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +217 -0
  88. package/src/app/pages/NamespacePage/index.jsx +199 -0
  89. package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
  90. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
  91. package/src/app/pages/NodePage/ClientCodePopover.jsx +46 -0
  92. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  93. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +149 -0
  94. package/src/app/pages/NodePage/Loadable.jsx +16 -0
  95. package/src/app/pages/NodePage/NodeColumnTab.jsx +200 -0
  96. package/src/app/pages/NodePage/NodeGraphTab.jsx +112 -0
  97. package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
  98. package/src/app/pages/NodePage/NodeInfoTab.jsx +212 -0
  99. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  100. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +233 -0
  101. package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
  102. package/src/app/pages/NodePage/NodeStatus.jsx +28 -0
  103. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  104. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
  105. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
  106. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
  107. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  108. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
  109. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
  110. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
  111. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +757 -0
  112. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  113. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +403 -0
  114. package/src/app/pages/NodePage/index.jsx +210 -0
  115. package/src/app/pages/NotFoundPage/Loadable.tsx +14 -0
  116. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  117. package/src/app/pages/NotFoundPage/index.tsx +23 -0
  118. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  119. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
  120. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +37 -0
  121. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  122. package/src/app/pages/Root/Loadable.tsx +14 -0
  123. package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
  124. package/src/app/pages/Root/assets/dj-logo.png +0 -0
  125. package/src/app/pages/Root/index.tsx +70 -0
  126. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  127. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  128. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  129. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  130. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  131. package/src/app/pages/TagPage/index.jsx +79 -0
  132. package/src/app/providers/djclient.jsx +5 -0
  133. package/src/app/services/DJService.js +665 -0
  134. package/src/app/services/__tests__/DJService.test.jsx +804 -0
  135. package/src/index.tsx +48 -0
  136. package/src/mocks/mockNodes.jsx +1430 -0
  137. package/src/react-app-env.d.ts +4 -0
  138. package/src/reportWebVitals.ts +15 -0
  139. package/src/setupTests.ts +36 -0
  140. package/src/styles/dag.css +228 -0
  141. package/src/styles/index.css +1083 -0
  142. package/src/styles/loading.css +34 -0
  143. package/src/styles/login.css +81 -0
  144. package/src/styles/node-creation.scss +197 -0
  145. package/src/styles/styles.scss +44 -0
  146. package/src/styles/styles.scss.d.ts +9 -0
  147. package/src/utils/__tests__/__snapshots__/loadable.test.tsx.snap +17 -0
  148. package/src/utils/__tests__/loadable.test.tsx +53 -0
  149. package/src/utils/__tests__/request.test.ts +82 -0
  150. package/src/utils/form.jsx +23 -0
  151. package/src/utils/loadable.tsx +30 -0
  152. package/src/utils/request.ts +54 -0
  153. package/tsconfig.json +34 -0
  154. package/webpack.config.js +118 -0
@@ -0,0 +1,161 @@
1
+ import { useContext, useEffect, useRef, useState } from 'react';
2
+ import * as React from 'react';
3
+ import DJClientContext from '../../providers/djclient';
4
+ import { ErrorMessage, Field, 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 AddMaterializationPopover({ node, onSubmit }) {
10
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
11
+ const [popoverAnchor, setPopoverAnchor] = useState(false);
12
+ const [engines, setEngines] = useState([]);
13
+ const [defaultEngine, setDefaultEngine] = useState('');
14
+
15
+ const ref = useRef(null);
16
+
17
+ useEffect(() => {
18
+ const fetchData = async () => {
19
+ const engines = await djClient.engines();
20
+ setEngines(engines);
21
+ setDefaultEngine(
22
+ engines && engines.length > 0
23
+ ? engines[0].name + '__' + engines[0].version
24
+ : '',
25
+ );
26
+ };
27
+ fetchData().catch(console.error);
28
+ const handleClickOutside = event => {
29
+ if (ref.current && !ref.current.contains(event.target)) {
30
+ setPopoverAnchor(false);
31
+ }
32
+ };
33
+ document.addEventListener('click', handleClickOutside, true);
34
+ return () => {
35
+ document.removeEventListener('click', handleClickOutside, true);
36
+ };
37
+ }, [djClient, setPopoverAnchor]);
38
+
39
+ const configureMaterialization = async (
40
+ values,
41
+ { setSubmitting, setStatus },
42
+ ) => {
43
+ setSubmitting(false);
44
+ const engineVersion = values.engine.split('__').slice(-1).join('');
45
+ const engineName = values.engine.split('__').slice(0, -1).join('');
46
+ const response = await djClient.materialize(
47
+ values.node,
48
+ engineName,
49
+ engineVersion,
50
+ values.schedule,
51
+ values.config,
52
+ );
53
+ if (response.status === 200 || response.status === 201) {
54
+ setStatus({ success: 'Saved!' });
55
+ } else {
56
+ setStatus({
57
+ failure: `${response.json.message}`,
58
+ });
59
+ }
60
+ onSubmit();
61
+ // window.location.reload();
62
+ };
63
+
64
+ return (
65
+ <>
66
+ <button
67
+ className="edit_button"
68
+ aria-label="PartitionColumn"
69
+ tabIndex="0"
70
+ onClick={() => {
71
+ setPopoverAnchor(!popoverAnchor);
72
+ }}
73
+ >
74
+ <span className="add_node">+ Add Materialization</span>
75
+ </button>
76
+ <div
77
+ className="fade modal-backdrop in"
78
+ style={{ display: popoverAnchor === false ? 'none' : 'block' }}
79
+ ></div>
80
+ <div
81
+ className="centerPopover"
82
+ role="dialog"
83
+ aria-label="client-code"
84
+ style={{
85
+ display: popoverAnchor === false ? 'none' : 'block',
86
+ width: '50%',
87
+ }}
88
+ ref={ref}
89
+ >
90
+ <Formik
91
+ initialValues={{
92
+ node: node?.name,
93
+ engine: defaultEngine,
94
+ config: '{"spark": {"spark.executor.memory": "6g"}}',
95
+ schedule: '@daily',
96
+ }}
97
+ onSubmit={configureMaterialization}
98
+ >
99
+ {function Render({ isSubmitting, status, setFieldValue }) {
100
+ return (
101
+ <Form>
102
+ <h2>Configure Materialization</h2>
103
+ {displayMessageAfterSubmit(status)}
104
+ <span data-testid="edit-partition">
105
+ <label htmlFor="engine">Engine</label>
106
+ <Field as="select" name="engine">
107
+ <>
108
+ {engines?.map(engine => (
109
+ <option value={engine.name + '__' + engine.version}>
110
+ {engine.name} {engine.version}
111
+ </option>
112
+ ))}
113
+ <option value=""></option>
114
+ </>
115
+ </Field>
116
+ </span>
117
+ <input
118
+ hidden={true}
119
+ name="node"
120
+ value={node?.name}
121
+ readOnly={true}
122
+ />
123
+ <br />
124
+ <br />
125
+ <label htmlFor="schedule">Schedule</label>
126
+ <Field
127
+ type="text"
128
+ name="schedule"
129
+ id="schedule"
130
+ placeholder="Cron"
131
+ default="@daily"
132
+ />
133
+ <br />
134
+ <br />
135
+ <div className="DescriptionInput">
136
+ <ErrorMessage name="description" component="span" />
137
+ <label htmlFor="Config">Config</label>
138
+ <Field
139
+ type="textarea"
140
+ as="textarea"
141
+ name="config"
142
+ id="Config"
143
+ placeholder="Optional engine-specific configuration (i.e., Spark conf etc)"
144
+ />
145
+ </div>
146
+ <button
147
+ className="add_node"
148
+ type="submit"
149
+ aria-label="SaveEditColumn"
150
+ aria-hidden="false"
151
+ >
152
+ Save
153
+ </button>
154
+ </Form>
155
+ );
156
+ }}
157
+ </Formik>
158
+ </div>
159
+ </>
160
+ );
161
+ }
@@ -0,0 +1,46 @@
1
+ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { nightOwl } from 'react-syntax-highlighter/src/styles/hljs';
4
+ import PythonIcon from '../../icons/PythonIcon';
5
+
6
+ export default function ClientCodePopover({ code }) {
7
+ const [codeAnchor, setCodeAnchor] = useState(false);
8
+ const ref = useRef(null);
9
+
10
+ useEffect(() => {
11
+ const handleClickOutside = event => {
12
+ if (ref.current && !ref.current.contains(event.target)) {
13
+ setCodeAnchor(false);
14
+ }
15
+ };
16
+ document.addEventListener('click', handleClickOutside, true);
17
+ return () => {
18
+ document.removeEventListener('click', handleClickOutside, true);
19
+ };
20
+ }, [setCodeAnchor]);
21
+
22
+ return (
23
+ <>
24
+ <button
25
+ className="code-button"
26
+ aria-label="code-button"
27
+ tabIndex="0"
28
+ height="45px"
29
+ onClick={() => setCodeAnchor(!codeAnchor)}
30
+ >
31
+ <PythonIcon />
32
+ </button>
33
+ <div
34
+ id={`node-create-code`}
35
+ role="dialog"
36
+ aria-label="client-code"
37
+ style={{ display: codeAnchor === false ? 'none' : 'block' }}
38
+ ref={ref}
39
+ >
40
+ <SyntaxHighlighter language="python" style={nightOwl}>
41
+ {code}
42
+ </SyntaxHighlighter>
43
+ </div>
44
+ </>
45
+ );
46
+ }
@@ -0,0 +1,116 @@
1
+ import { useContext, useEffect, useRef, 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
+ const ref = useRef(null);
13
+
14
+ useEffect(() => {
15
+ const handleClickOutside = event => {
16
+ if (ref.current && !ref.current.contains(event.target)) {
17
+ setPopoverAnchor(false);
18
+ }
19
+ };
20
+ document.addEventListener('click', handleClickOutside, true);
21
+ return () => {
22
+ document.removeEventListener('click', handleClickOutside, true);
23
+ };
24
+ }, [setPopoverAnchor]);
25
+
26
+ const saveAttributes = async (
27
+ { node, column, attributes },
28
+ { setSubmitting, setStatus },
29
+ ) => {
30
+ setSubmitting(false);
31
+ const response = await djClient.setAttributes(node, column, attributes);
32
+ if (response.status === 200 || response.status === 201) {
33
+ setStatus({ success: 'Saved!' });
34
+ } else {
35
+ setStatus({
36
+ failure: `${response.json.message}`,
37
+ });
38
+ }
39
+ onSubmit();
40
+ // window.location.reload();
41
+ };
42
+
43
+ return (
44
+ <>
45
+ <button
46
+ className="edit_button"
47
+ aria-label="EditColumn"
48
+ tabIndex="0"
49
+ onClick={() => {
50
+ setPopoverAnchor(!popoverAnchor);
51
+ }}
52
+ >
53
+ <EditIcon />
54
+ </button>
55
+ <div
56
+ className="popover"
57
+ role="dialog"
58
+ aria-label="client-code"
59
+ style={{ display: popoverAnchor === false ? 'none' : 'block' }}
60
+ ref={ref}
61
+ >
62
+ <Formik
63
+ initialValues={{
64
+ column: column.name,
65
+ node: node.name,
66
+ attributes: [],
67
+ }}
68
+ onSubmit={saveAttributes}
69
+ >
70
+ {function Render({ isSubmitting, status, setFieldValue }) {
71
+ return (
72
+ <Form>
73
+ {displayMessageAfterSubmit(status)}
74
+ <span data-testid="edit-attributes">
75
+ <FormikSelect
76
+ selectOptions={options}
77
+ formikFieldName="attributes"
78
+ placeholder="Select column attributes"
79
+ className=""
80
+ defaultValue={column.attributes.map(attr => {
81
+ return {
82
+ value: attr.attribute_type.name,
83
+ label: labelize(attr.attribute_type.name),
84
+ };
85
+ })}
86
+ isMulti={true}
87
+ />
88
+ </span>
89
+ <input
90
+ hidden={true}
91
+ name="column"
92
+ value={column.name}
93
+ readOnly={true}
94
+ />
95
+ <input
96
+ hidden={true}
97
+ name="node"
98
+ value={node.name}
99
+ readOnly={true}
100
+ />
101
+ <button
102
+ className="add_node"
103
+ type="submit"
104
+ aria-label="SaveEditColumn"
105
+ aria-hidden="false"
106
+ >
107
+ Save
108
+ </button>
109
+ </Form>
110
+ );
111
+ }}
112
+ </Formik>
113
+ </div>
114
+ </>
115
+ );
116
+ }
@@ -0,0 +1,149 @@
1
+ import { useContext, useEffect, useRef, 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 ref = useRef(null);
18
+
19
+ useEffect(() => {
20
+ const handleClickOutside = event => {
21
+ if (ref.current && !ref.current.contains(event.target)) {
22
+ setPopoverAnchor(false);
23
+ }
24
+ };
25
+ document.addEventListener('click', handleClickOutside, true);
26
+ return () => {
27
+ document.removeEventListener('click', handleClickOutside, true);
28
+ };
29
+ }, [setPopoverAnchor]);
30
+
31
+ const columnDimension = column.dimension;
32
+
33
+ const handleSubmit = async (
34
+ { node, column, dimension },
35
+ { setSubmitting, setStatus },
36
+ ) => {
37
+ setSubmitting(false);
38
+ if (columnDimension?.name && dimension === 'Remove') {
39
+ await unlinkDimension(node, column, columnDimension?.name, setStatus);
40
+ } else {
41
+ await linkDimension(node, column, dimension, setStatus);
42
+ }
43
+ onSubmit();
44
+ };
45
+
46
+ const linkDimension = async (node, column, dimension, setStatus) => {
47
+ const response = await djClient.linkDimension(node, column, dimension);
48
+ if (response.status === 200 || response.status === 201) {
49
+ setStatus({ success: 'Saved!' });
50
+ } else {
51
+ setStatus({
52
+ failure: `${response.json.message}`,
53
+ });
54
+ }
55
+ };
56
+
57
+ const unlinkDimension = async (node, column, currentDimension, setStatus) => {
58
+ const response = await djClient.unlinkDimension(
59
+ node,
60
+ column,
61
+ currentDimension,
62
+ );
63
+ if (response.status === 200 || response.status === 201) {
64
+ setStatus({ success: 'Removed dimension link!' });
65
+ } else {
66
+ setStatus({
67
+ failure: `${response.json.message}`,
68
+ });
69
+ }
70
+ };
71
+
72
+ return (
73
+ <>
74
+ <button
75
+ className="edit_button"
76
+ aria-label="LinkDimension"
77
+ tabIndex="0"
78
+ onClick={() => {
79
+ setPopoverAnchor(!popoverAnchor);
80
+ }}
81
+ >
82
+ <EditIcon />
83
+ </button>
84
+ <div
85
+ className="popover"
86
+ role="dialog"
87
+ aria-label="client-code"
88
+ style={{ display: popoverAnchor === false ? 'none' : 'block' }}
89
+ ref={ref}
90
+ >
91
+ <Formik
92
+ initialValues={{
93
+ column: column.name,
94
+ node: node.name,
95
+ dimension: '',
96
+ currentDimension: column.dimension?.name,
97
+ }}
98
+ onSubmit={handleSubmit}
99
+ >
100
+ {function Render({ isSubmitting, status, setFieldValue }) {
101
+ return (
102
+ <Form>
103
+ {displayMessageAfterSubmit(status)}
104
+ <span data-testid="link-dimension">
105
+ <FormikSelect
106
+ selectOptions={[
107
+ { value: 'Remove', label: '[Remove Dimension]' },
108
+ ].concat(options)}
109
+ formikFieldName="dimension"
110
+ placeholder="Select dimension to link"
111
+ className=""
112
+ defaultValue={
113
+ column.dimension
114
+ ? {
115
+ value: column.dimension.name,
116
+ label: column.dimension.name,
117
+ }
118
+ : ''
119
+ }
120
+ />
121
+ </span>
122
+ <input
123
+ hidden={true}
124
+ name="column"
125
+ value={column.name}
126
+ readOnly={true}
127
+ />
128
+ <input
129
+ hidden={true}
130
+ name="node"
131
+ value={node.name}
132
+ readOnly={true}
133
+ />
134
+ <button
135
+ className="add_node"
136
+ type="submit"
137
+ aria-label="SaveLinkDimension"
138
+ aria-hidden="false"
139
+ >
140
+ Save
141
+ </button>
142
+ </Form>
143
+ );
144
+ }}
145
+ </Formik>
146
+ </div>
147
+ </>
148
+ );
149
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Asynchronously loads the component for the Node page
3
+ */
4
+
5
+ import * as React from 'react';
6
+ import { lazyLoad } from '../../../utils/loadable';
7
+
8
+ export const NodePage = props => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.NodePage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )(props);
16
+ };
@@ -0,0 +1,200 @@
1
+ import { useEffect, useState } from 'react';
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';
7
+ import PartitionColumnPopover from './PartitionColumnPopover';
8
+
9
+ export default function NodeColumnTab({ node, djClient }) {
10
+ const [attributes, setAttributes] = useState([]);
11
+ const [dimensions, setDimensions] = useState([]);
12
+ const [columns, setColumns] = useState([]);
13
+ useEffect(() => {
14
+ const fetchData = async () => {
15
+ setColumns(await djClient.columns(node));
16
+ };
17
+ fetchData().catch(console.error);
18
+ }, [djClient, node]);
19
+
20
+ useEffect(() => {
21
+ const fetchData = async () => {
22
+ const attributes = await djClient.attributes();
23
+ const options = attributes.map(attr => {
24
+ return { value: attr.name, label: labelize(attr.name) };
25
+ });
26
+ setAttributes(options);
27
+ };
28
+ fetchData().catch(console.error);
29
+ }, [djClient]);
30
+
31
+ useEffect(() => {
32
+ const fetchData = async () => {
33
+ const dimensions = await djClient.dimensions();
34
+ const options = dimensions.map(name => {
35
+ return { value: name, label: name };
36
+ });
37
+ setDimensions(options);
38
+ };
39
+ fetchData().catch(console.error);
40
+ }, [djClient]);
41
+
42
+ const showColumnAttributes = col => {
43
+ return col.attributes.map((attr, idx) => (
44
+ <span
45
+ className="node_type__dimension badge node_type"
46
+ key={`col-attr-${col.name}-${idx}`}
47
+ >
48
+ {attr.attribute_type.name.replace(/_/, ' ')}
49
+ </span>
50
+ ));
51
+ };
52
+
53
+ const showColumnPartition = col => {
54
+ if (col.partition) {
55
+ return (
56
+ <>
57
+ <span
58
+ className="node_type badge node_type__blank"
59
+ key={`col-attr-partition-type`}
60
+ >
61
+ <span
62
+ className="partition_value badge"
63
+ key={`col-attr-partition-type`}
64
+ >
65
+ <b>Type:</b> {col.partition.type_}
66
+ </span>
67
+ <br />
68
+ <span
69
+ className="partition_value badge"
70
+ key={`col-attr-partition-type`}
71
+ >
72
+ <b>Format:</b> <code>{col.partition.format}</code>
73
+ </span>
74
+ <br />
75
+ <span
76
+ className="partition_value badge"
77
+ key={`col-attr-partition-type`}
78
+ >
79
+ <b>Granularity:</b> <code>{col.partition.granularity}</code>
80
+ </span>
81
+ </span>
82
+ </>
83
+ );
84
+ }
85
+ return '';
86
+ };
87
+
88
+ const columnList = columns => {
89
+ return columns.map(col => (
90
+ <tr key={col.name}>
91
+ <td
92
+ className="text-start"
93
+ role="columnheader"
94
+ aria-label="ColumnName"
95
+ aria-hidden="false"
96
+ >
97
+ {col.name}
98
+ </td>
99
+ <td>
100
+ <span
101
+ className=""
102
+ role="columnheader"
103
+ aria-label="ColumnDisplayName"
104
+ aria-hidden="false"
105
+ >
106
+ {col.display_name}
107
+ </span>
108
+ </td>
109
+ <td>
110
+ <span
111
+ className={`node_type__${
112
+ node.type === 'cube' ? col.type : 'transform'
113
+ } badge node_type`}
114
+ role="columnheader"
115
+ aria-label="ColumnType"
116
+ aria-hidden="false"
117
+ >
118
+ {col.type}
119
+ </span>
120
+ </td>
121
+ {node.type !== 'cube' ? (
122
+ <td>
123
+ {col.dimension !== undefined && col.dimension !== null ? (
124
+ <>
125
+ <a href={`/nodes/${col.dimension.name}`}>
126
+ {col.dimension.name}
127
+ </a>
128
+ <ClientCodePopover code={col.clientCode} />
129
+ </>
130
+ ) : (
131
+ ''
132
+ )}{' '}
133
+ <LinkDimensionPopover
134
+ column={col}
135
+ node={node}
136
+ options={dimensions}
137
+ onSubmit={async () => {
138
+ const res = await djClient.node(node.name);
139
+ setColumns(res.columns);
140
+ }}
141
+ />
142
+ </td>
143
+ ) : (
144
+ ''
145
+ )}
146
+ {node.type !== 'cube' ? (
147
+ <td>
148
+ {showColumnAttributes(col)}
149
+ <EditColumnPopover
150
+ column={col}
151
+ node={node}
152
+ options={attributes}
153
+ onSubmit={async () => {
154
+ const res = await djClient.node(node.name);
155
+ setColumns(res.columns);
156
+ }}
157
+ />
158
+ </td>
159
+ ) : (
160
+ ''
161
+ )}
162
+ <td>
163
+ {showColumnPartition(col)}
164
+ <PartitionColumnPopover
165
+ column={col}
166
+ node={node}
167
+ onSubmit={async () => {
168
+ const res = await djClient.node(node.name);
169
+ setColumns(res.columns);
170
+ }}
171
+ />
172
+ </td>
173
+ </tr>
174
+ ));
175
+ };
176
+
177
+ return (
178
+ <div className="table-responsive">
179
+ <table className="card-inner-table table">
180
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
181
+ <tr>
182
+ <th className="text-start">Column</th>
183
+ <th>Display Name</th>
184
+ <th>Type</th>
185
+ {node?.type !== 'cube' ? (
186
+ <>
187
+ <th>Linked Dimension</th>
188
+ <th>Attributes</th>
189
+ </>
190
+ ) : (
191
+ ''
192
+ )}
193
+ <th>Partition</th>
194
+ </tr>
195
+ </thead>
196
+ <tbody>{columnList(columns)}</tbody>
197
+ </table>
198
+ </div>
199
+ );
200
+ }