datajunction-ui 0.0.1-rc.2 → 0.0.1-rc.21

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 (136) hide show
  1. package/.env +1 -0
  2. package/.prettierignore +3 -1
  3. package/dj-logo.svg +10 -0
  4. package/package.json +43 -13
  5. package/public/favicon.ico +0 -0
  6. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  7. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -39
  8. package/src/app/components/DeleteNode.jsx +79 -0
  9. package/src/app/components/ListGroupItem.jsx +8 -1
  10. package/src/app/components/NamespaceHeader.jsx +4 -13
  11. package/src/app/components/QueryInfo.jsx +77 -0
  12. package/src/app/components/Tab.jsx +3 -2
  13. package/src/app/components/ToggleSwitch.jsx +20 -0
  14. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  15. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  16. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  17. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +3 -0
  18. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  19. package/src/app/components/djgraph/Collapse.jsx +46 -0
  20. package/src/app/components/djgraph/DJNode.jsx +60 -82
  21. package/src/app/components/djgraph/DJNodeColumns.jsx +71 -0
  22. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  23. package/src/app/components/djgraph/LayoutFlow.jsx +104 -0
  24. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  25. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  26. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  27. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  28. package/src/app/constants.js +2 -0
  29. package/src/app/icons/AlertIcon.jsx +32 -0
  30. package/src/app/icons/CollapsedIcon.jsx +15 -0
  31. package/src/app/icons/DJLogo.jsx +36 -0
  32. package/src/app/icons/DeleteIcon.jsx +21 -0
  33. package/src/app/icons/EditIcon.jsx +18 -0
  34. package/src/app/icons/ExpandedIcon.jsx +15 -0
  35. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  36. package/src/app/icons/InvalidIcon.jsx +14 -0
  37. package/src/app/icons/PythonIcon.jsx +52 -0
  38. package/src/app/icons/TableIcon.jsx +14 -0
  39. package/src/app/icons/ValidIcon.jsx +14 -0
  40. package/src/app/index.tsx +79 -26
  41. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +46 -0
  42. package/src/app/pages/AddEditNodePage/FullNameField.jsx +37 -0
  43. package/src/app/pages/AddEditNodePage/Loadable.jsx +16 -0
  44. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +89 -0
  45. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +77 -0
  46. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +93 -0
  47. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  48. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  49. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  50. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +53 -0
  51. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +53 -0
  52. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  53. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +178 -0
  54. package/src/app/pages/AddEditNodePage/index.jsx +357 -0
  55. package/src/app/pages/LoginPage/__tests__/index.test.jsx +70 -0
  56. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  57. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  58. package/src/app/pages/LoginPage/index.jsx +90 -0
  59. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +86 -0
  60. package/src/app/pages/NamespacePage/Explorer.jsx +57 -0
  61. package/src/app/pages/NamespacePage/Loadable.jsx +9 -7
  62. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +95 -0
  63. package/src/app/pages/NamespacePage/index.jsx +132 -31
  64. package/src/app/pages/NodePage/ClientCodePopover.jsx +32 -0
  65. package/src/app/pages/NodePage/EditColumnPopover.jsx +102 -0
  66. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +135 -0
  67. package/src/app/pages/NodePage/Loadable.jsx +9 -7
  68. package/src/app/pages/NodePage/NodeColumnTab.jsx +106 -27
  69. package/src/app/pages/NodePage/NodeGraphTab.jsx +94 -148
  70. package/src/app/pages/NodePage/NodeHistory.jsx +212 -0
  71. package/src/app/pages/NodePage/NodeInfoTab.jsx +166 -51
  72. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  73. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +174 -0
  74. package/src/app/pages/NodePage/NodeSQLTab.jsx +82 -0
  75. package/src/app/pages/NodePage/NodeStatus.jsx +14 -20
  76. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  77. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +49 -0
  78. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  79. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +165 -0
  80. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +591 -0
  81. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +57 -0
  82. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +725 -0
  83. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  84. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +402 -0
  85. package/src/app/pages/NodePage/index.jsx +151 -41
  86. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  87. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  88. package/src/app/pages/RegisterTablePage/index.jsx +163 -0
  89. package/src/app/pages/Root/__tests__/index.test.jsx +77 -0
  90. package/src/app/pages/Root/index.tsx +32 -4
  91. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  92. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  93. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  94. package/src/app/providers/djclient.jsx +5 -0
  95. package/src/app/services/DJService.js +398 -22
  96. package/src/app/services/__tests__/DJService.test.jsx +609 -0
  97. package/src/mocks/mockNodes.jsx +1397 -0
  98. package/src/setupTests.ts +31 -1
  99. package/src/styles/dag.css +111 -5
  100. package/src/styles/index.css +467 -31
  101. package/src/styles/login.css +67 -0
  102. package/src/styles/node-creation.scss +197 -0
  103. package/src/styles/styles.scss +44 -0
  104. package/src/styles/styles.scss.d.ts +9 -0
  105. package/src/utils/form.jsx +23 -0
  106. package/tsconfig.json +1 -5
  107. package/webpack.config.js +29 -6
  108. package/.babelrc +0 -4
  109. package/.env.local +0 -4
  110. package/.env.production +0 -1
  111. package/.github/pull_request_template.md +0 -11
  112. package/.github/workflows/ci.yml +0 -33
  113. package/.vscode/extensions.json +0 -7
  114. package/.vscode/launch.json +0 -15
  115. package/.vscode/settings.json +0 -25
  116. package/Dockerfile +0 -7
  117. package/dist/5fa71a03d45dc2e3d61447f3013a303d.png +0 -0
  118. package/dist/index.html +0 -1
  119. package/dist/main.js +0 -23303
  120. package/dist/static/main.05a86d446163fd5f17d3.js +0 -2
  121. package/dist/static/main.05a86d446163fd5f17d3.js.LICENSE.txt +0 -98
  122. package/dist/static/main.9e53bed734dae98e5b10.js +0 -2
  123. package/dist/static/main.9e53bed734dae98e5b10.js.LICENSE.txt +0 -98
  124. package/dist/static/main.js +0 -2
  125. package/dist/static/main.js.LICENSE.txt +0 -98
  126. package/dist/static/vendor.05a86d446163fd5f17d3.js +0 -2
  127. package/dist/static/vendor.05a86d446163fd5f17d3.js.LICENSE.txt +0 -29
  128. package/dist/static/vendor.9e53bed734dae98e5b10.js +0 -2
  129. package/dist/static/vendor.9e53bed734dae98e5b10.js.LICENSE.txt +0 -29
  130. package/dist/static/vendor.js +0 -2
  131. package/dist/static/vendor.js.LICENSE.txt +0 -29
  132. package/dist/vendor.js +0 -281
  133. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  134. package/src/app/pages/ListNamespacesPage/index.jsx +0 -52
  135. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  136. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -1,14 +1,23 @@
1
1
  import * as React from 'react';
2
2
  import { useParams } from 'react-router-dom';
3
- import { useEffect, useState } from 'react';
4
- import { DataJunctionAPI } from '../../services/DJService';
3
+ import { useContext, useEffect, useState } from 'react';
5
4
  import Tab from '../../components/Tab';
6
5
  import NamespaceHeader from '../../components/NamespaceHeader';
7
6
  import NodeInfoTab from './NodeInfoTab';
8
7
  import NodeColumnTab from './NodeColumnTab';
9
8
  import NodeLineage from './NodeGraphTab';
9
+ import NodeHistory from './NodeHistory';
10
+ import DJClientContext from '../../providers/djclient';
11
+ import NodeSQLTab from './NodeSQLTab';
12
+ import NodeMaterializationTab from './NodeMaterializationTab';
13
+ import ClientCodePopover from './ClientCodePopover';
14
+ import NodesWithDimension from './NodesWithDimension';
15
+ import NodeColumnLineage from './NodeLineageTab';
16
+ import EditIcon from '../../icons/EditIcon';
17
+ import AlertIcon from '../../icons/AlertIcon';
10
18
 
11
19
  export function NodePage() {
20
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
12
21
  const [state, setState] = useState({
13
22
  selectedTab: 0,
14
23
  });
@@ -20,7 +29,7 @@ export function NodePage() {
20
29
  };
21
30
 
22
31
  const buildTabs = tab => {
23
- return (
32
+ return tab.display ? (
24
33
  <Tab
25
34
  key={tab.id}
26
35
  id={tab.id}
@@ -28,72 +37,173 @@ export function NodePage() {
28
37
  onClick={onClickTab(tab.id)}
29
38
  selectedTab={state.selectedTab}
30
39
  />
31
- );
40
+ ) : null;
32
41
  };
33
42
 
34
43
  const { name } = useParams();
35
44
 
36
45
  useEffect(() => {
37
46
  const fetchData = async () => {
38
- const data = await DataJunctionAPI.node(name);
47
+ const data = await djClient.node(name);
48
+ data.createNodeClientCode = await djClient.clientCode(name);
39
49
  setNode(data);
50
+ if (data.type === 'metric') {
51
+ const metric = await djClient.metric(name);
52
+ data.dimensions = metric.dimensions;
53
+ setNode(data);
54
+ }
55
+ if (data.type === 'cube') {
56
+ const cube = await djClient.cube(name);
57
+ data.cube_elements = cube.cube_elements;
58
+ setNode(data);
59
+ }
40
60
  };
41
61
  fetchData().catch(console.error);
42
- }, [name]);
62
+ }, [djClient, name]);
63
+
64
+ const tabsList = node => {
65
+ return [
66
+ {
67
+ id: 0,
68
+ name: 'Info',
69
+ display: true,
70
+ },
71
+ {
72
+ id: 1,
73
+ name: 'Columns',
74
+ display: true,
75
+ },
76
+ {
77
+ id: 2,
78
+ name: 'Graph',
79
+ display: true,
80
+ },
81
+ {
82
+ id: 3,
83
+ name: 'History',
84
+ display: true,
85
+ },
86
+ {
87
+ id: 4,
88
+ name: 'SQL',
89
+ display: node?.type !== 'dimension' && node?.type !== 'source',
90
+ },
91
+ {
92
+ id: 5,
93
+ name: 'Materializations',
94
+ display: node?.type !== 'source',
95
+ },
96
+ {
97
+ id: 6,
98
+ name: 'Linked Nodes',
99
+ display: node?.type === 'dimension',
100
+ },
101
+ {
102
+ id: 7,
103
+ name: 'Lineage',
104
+ display: node?.type === 'metric',
105
+ },
106
+ ];
107
+ };
43
108
 
44
- const TabsJson = [
45
- {
46
- id: 0,
47
- name: 'Info',
48
- },
49
- {
50
- id: 1,
51
- name: 'Columns',
52
- },
53
- {
54
- id: 2,
55
- name: 'Graph',
56
- },
57
- ];
58
109
  //
59
110
  //
60
111
  let tabToDisplay = null;
61
112
  switch (state.selectedTab) {
62
113
  case 0:
63
- tabToDisplay = node ? <NodeInfoTab node={node} /> : '';
114
+ tabToDisplay =
115
+ node && node.message === undefined ? <NodeInfoTab node={node} /> : '';
64
116
  break;
65
117
  case 1:
66
- tabToDisplay = <NodeColumnTab node={node} />;
118
+ tabToDisplay = <NodeColumnTab node={node} djClient={djClient} />;
67
119
  break;
68
120
  case 2:
69
- tabToDisplay = <NodeLineage djNode={node} />;
121
+ tabToDisplay = <NodeLineage djNode={node} djClient={djClient} />;
122
+ break;
123
+ case 3:
124
+ tabToDisplay = <NodeHistory node={node} djClient={djClient} />;
125
+ break;
126
+ case 4:
127
+ tabToDisplay =
128
+ node?.type === 'metric' ? <NodeSQLTab djNode={node} /> : <br />;
129
+ break;
130
+ case 5:
131
+ tabToDisplay = <NodeMaterializationTab node={node} djClient={djClient} />;
70
132
  break;
71
- default:
133
+ case 6:
134
+ tabToDisplay = <NodesWithDimension node={node} djClient={djClient} />;
135
+ break;
136
+ case 7:
137
+ tabToDisplay = <NodeColumnLineage djNode={node} djClient={djClient} />;
138
+ break;
139
+ default: /* istanbul ignore next */
72
140
  tabToDisplay = <NodeInfoTab node={node} />;
73
141
  }
74
-
75
142
  // @ts-ignore
76
143
  return (
77
144
  <div className="node__header">
78
145
  <NamespaceHeader namespace={name.split('.').slice(0, -1).join('.')} />
79
146
  <div className="card">
80
- <div className="card-header">
81
- <h3 className="card-title align-items-start flex-column">
82
- <span className="card-label fw-bold text-gray-800">
83
- {node?.display_name}
84
- </span>
85
- </h3>
86
- <span
87
- className="fs-6 fw-semibold text-gray-400"
88
- style={{ marginTop: '-4rem' }}
89
- >
90
- Updated {new Date(node?.updated_at).toDateString()}
91
- </span>
92
- <div className="align-items-center row">
93
- {TabsJson.map(buildTabs)}
147
+ {node?.message === undefined ? (
148
+ <div className="card-header">
149
+ <h3
150
+ className="card-title align-items-start flex-column"
151
+ style={{ display: 'inline-block' }}
152
+ >
153
+ <span
154
+ className="card-label fw-bold text-gray-800"
155
+ role="dialog"
156
+ aria-hidden="false"
157
+ aria-label="DisplayName"
158
+ >
159
+ {node?.display_name}{' '}
160
+ <span
161
+ className={'node_type__' + node?.type + ' badge node_type'}
162
+ role="dialog"
163
+ aria-hidden="false"
164
+ aria-label="NodeType"
165
+ >
166
+ {node?.type}
167
+ </span>
168
+ </span>
169
+ </h3>
170
+ <a
171
+ href={`/nodes/${node?.name}/edit`}
172
+ style={{ marginLeft: '0.5rem' }}
173
+ >
174
+ <EditIcon />
175
+ </a>
176
+ <ClientCodePopover code={node?.createNodeClientCode} />
177
+ <div>
178
+ <a
179
+ href={'/nodes/' + node?.name}
180
+ className="link-table"
181
+ role="dialog"
182
+ aria-hidden="false"
183
+ aria-label="NodeName"
184
+ >
185
+ {node?.name}
186
+ </a>
187
+ <span
188
+ className="rounded-pill badge bg-secondary-soft"
189
+ style={{ marginLeft: '0.5rem' }}
190
+ >
191
+ {node?.version}
192
+ </span>
193
+ </div>
194
+ <div className="align-items-center row">
195
+ {tabsList(node).map(buildTabs)}
196
+ </div>
197
+ {tabToDisplay}
198
+ </div>
199
+ ) : node?.message !== undefined ? (
200
+ <div className="message alert" style={{ margin: '20px' }}>
201
+ <AlertIcon />
202
+ Node `{name}` does not exist!
94
203
  </div>
95
- {tabToDisplay}
96
- </div>
204
+ ) : (
205
+ ''
206
+ )}
97
207
  </div>
98
208
  </div>
99
209
  );
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { render } from '@testing-library/react';
3
+ import { NotFoundPage } from '../index';
4
+ import { HelmetProvider } from 'react-helmet-async';
5
+
6
+ describe('<NotFoundPage />', () => {
7
+ it('displays the correct 404 message ', () => {
8
+ const { getByText } = render(
9
+ <HelmetProvider>
10
+ <NotFoundPage />
11
+ </HelmetProvider>,
12
+ );
13
+
14
+ expect(getByText('Page not found.')).toBeInTheDocument();
15
+ });
16
+ });
@@ -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 RegisterTablePage = () => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.RegisterTablePage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )();
16
+ };
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Node add + edit page for transforms, metrics, and dimensions. The creation and edit flow for these
3
+ * node types is largely the same, with minor differences handled server-side. For the `query`
4
+ * field, this page will render a CodeMirror SQL editor with autocompletion and syntax highlighting.
5
+ */
6
+ import { ErrorMessage, Field, Form, Formik } from 'formik';
7
+
8
+ import NamespaceHeader from '../../components/NamespaceHeader';
9
+ import React, { useContext, useEffect, useState } from 'react';
10
+ import DJClientContext from '../../providers/djclient';
11
+ import 'styles/node-creation.scss';
12
+ import AlertIcon from '../../icons/AlertIcon';
13
+ import ValidIcon from '../../icons/ValidIcon';
14
+ import { FormikSelect } from '../AddEditNodePage/FormikSelect';
15
+
16
+ export function RegisterTablePage() {
17
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
18
+ const [catalogs, setCatalogs] = useState([]);
19
+
20
+ useEffect(() => {
21
+ const fetchData = async () => {
22
+ const catalogs = await djClient.catalogs();
23
+ setCatalogs(
24
+ catalogs.map(catalog => {
25
+ return { value: catalog.name, label: catalog.name };
26
+ }),
27
+ );
28
+ };
29
+ fetchData().catch(console.error);
30
+ }, [djClient, djClient.namespaces]);
31
+
32
+ const initialValues = {
33
+ catalog: '',
34
+ schema: '',
35
+ table: '',
36
+ };
37
+
38
+ const validator = values => {
39
+ const errors = {};
40
+ if (!values.table) {
41
+ errors.table = 'Required';
42
+ }
43
+ if (!values.schema) {
44
+ errors.schema = 'Required';
45
+ }
46
+ return errors;
47
+ };
48
+
49
+ const handleSubmit = async (values, { setSubmitting, setStatus }) => {
50
+ const { status, json } = await djClient.registerTable(
51
+ values.catalog,
52
+ values.schema,
53
+ values.table,
54
+ );
55
+ if (status === 200 || status === 201) {
56
+ setStatus({
57
+ success: (
58
+ <>
59
+ Successfully registered source node{' '}
60
+ <a href={`/nodes/${json.name}`}>{json.name}</a>, which references
61
+ table {values.catalog}.{values.schema}.{values.table}.
62
+ </>
63
+ ),
64
+ });
65
+ } else {
66
+ setStatus({
67
+ failure: `${json.message}`,
68
+ });
69
+ }
70
+ setSubmitting(false);
71
+ window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
72
+ };
73
+
74
+ const displayMessageAfterSubmit = status => {
75
+ return status?.success !== undefined ? (
76
+ <div className="message success">
77
+ <ValidIcon />
78
+ {status?.success}
79
+ </div>
80
+ ) : status?.failure !== undefined ? (
81
+ alertMessage(status?.failure)
82
+ ) : (
83
+ ''
84
+ );
85
+ };
86
+
87
+ const alertMessage = message => {
88
+ return (
89
+ <div className="message alert">
90
+ <AlertIcon />
91
+ {message}
92
+ </div>
93
+ );
94
+ };
95
+
96
+ return (
97
+ <div className="mid">
98
+ <NamespaceHeader namespace="" />
99
+ <div className="card">
100
+ <div className="card-header">
101
+ <h2>
102
+ Register{' '}
103
+ <span className={`node_type__source node_type_creation_heading`}>
104
+ Source
105
+ </span>
106
+ </h2>
107
+ <center>
108
+ <Formik
109
+ initialValues={initialValues}
110
+ validate={validator}
111
+ onSubmit={handleSubmit}
112
+ >
113
+ {function Render({ isSubmitting, status, setFieldValue }) {
114
+ return (
115
+ <Form>
116
+ {displayMessageAfterSubmit(status)}
117
+ {
118
+ <>
119
+ <div className="SourceCreationInput">
120
+ <ErrorMessage name="catalog" component="span" />
121
+ <label htmlFor="catalog">Catalog</label>
122
+ <FormikSelect
123
+ selectOptions={catalogs}
124
+ formikFieldName="catalog"
125
+ placeholder="Choose Catalog"
126
+ defaultValue={catalogs[0]}
127
+ />
128
+ </div>
129
+ <div className="SourceCreationInput">
130
+ <ErrorMessage name="schema" component="span" />
131
+ <label htmlFor="schema">Schema</label>
132
+ <Field
133
+ type="text"
134
+ name="schema"
135
+ id="schema"
136
+ placeholder="Schema"
137
+ />
138
+ </div>
139
+ <div className="SourceCreationInput NodeCreationInput">
140
+ <ErrorMessage name="table" component="span" />
141
+ <label htmlFor="table">Table</label>
142
+ <Field
143
+ type="text"
144
+ name="table"
145
+ id="table"
146
+ placeholder="Table"
147
+ />
148
+ </div>
149
+ <button type="submit" disabled={isSubmitting}>
150
+ Register
151
+ </button>
152
+ </>
153
+ }
154
+ </Form>
155
+ );
156
+ }}
157
+ </Formik>
158
+ </center>
159
+ </div>
160
+ </div>
161
+ </div>
162
+ );
163
+ }
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import { render, screen, waitFor } from '@testing-library/react';
3
+ import { Root } from '../index';
4
+ import DJClientContext from '../../../providers/djclient';
5
+ import { HelmetProvider } from 'react-helmet-async';
6
+
7
+ describe('<Root />', () => {
8
+ const mockDjClient = {
9
+ logout: jest.fn(),
10
+ };
11
+
12
+ it('renders with the correct title and navigation', async () => {
13
+ render(
14
+ <HelmetProvider>
15
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
16
+ <Root />
17
+ </DJClientContext.Provider>
18
+ </HelmetProvider>,
19
+ );
20
+
21
+ waitFor(() => {
22
+ expect(document.title).toEqual('DataJunction');
23
+ const metaDescription = document.querySelector(
24
+ "meta[name='description']",
25
+ );
26
+ expect(metaDescription).toBeInTheDocument();
27
+ expect(metaDescription.content).toBe(
28
+ 'DataJunction Metrics Platform Webapp',
29
+ );
30
+
31
+ expect(screen.getByText(/^DataJunction$/)).toBeInTheDocument();
32
+ expect(screen.getByText('Explore').closest('a')).toHaveAttribute(
33
+ 'href',
34
+ '/',
35
+ );
36
+ expect(screen.getByText('SQL').closest('a')).toHaveAttribute(
37
+ 'href',
38
+ '/sql',
39
+ );
40
+ expect(screen.getByText('Docs').closest('a')).toHaveAttribute(
41
+ 'href',
42
+ 'https://www.datajunction.io',
43
+ );
44
+ });
45
+ });
46
+
47
+ it('renders Logout button unless REACT_DISABLE_AUTH is true', () => {
48
+ process.env.REACT_DISABLE_AUTH = 'false';
49
+ render(
50
+ <HelmetProvider>
51
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
52
+ <Root />
53
+ </DJClientContext.Provider>
54
+ </HelmetProvider>,
55
+ );
56
+ expect(screen.getByText('Logout')).toBeInTheDocument();
57
+ });
58
+
59
+ it('calls logout and reloads window on logout button click', () => {
60
+ process.env.REACT_DISABLE_AUTH = 'false';
61
+ const originalLocation = window.location;
62
+ delete window.location;
63
+ window.location = { ...originalLocation, reload: jest.fn() };
64
+
65
+ render(
66
+ <HelmetProvider>
67
+ <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
68
+ <Root />
69
+ </DJClientContext.Provider>
70
+ </HelmetProvider>,
71
+ );
72
+
73
+ screen.getByText('Logout').click();
74
+ expect(mockDjClient.logout).toHaveBeenCalled();
75
+ window.location = originalLocation;
76
+ });
77
+ });
@@ -1,8 +1,16 @@
1
+ import { useContext } from 'react';
1
2
  import { Outlet } from 'react-router-dom';
2
- import logo from './assets/dj-logo.png';
3
+ import DJLogo from '../../icons/DJLogo';
3
4
  import { Helmet } from 'react-helmet-async';
5
+ import DJClientContext from '../../providers/djclient';
4
6
 
5
7
  export function Root() {
8
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
9
+
10
+ const handleLogout = async () => {
11
+ await djClient.logout();
12
+ window.location.reload();
13
+ };
6
14
  return (
7
15
  <>
8
16
  <Helmet>
@@ -16,8 +24,8 @@ export function Root() {
16
24
  <div className="header">
17
25
  <div className="logo">
18
26
  <h2>
19
- <img src={logo} alt="DJ Logo" width="15%" />
20
- DataJunction
27
+ <DJLogo />
28
+ Data<b>Junction</b>
21
29
  </h2>
22
30
  </div>
23
31
  <div className="menu">
@@ -29,12 +37,32 @@ export function Root() {
29
37
  </span>
30
38
  <span className="menu-link">
31
39
  <span className="menu-title">
32
- <a href="/">Help</a>
40
+ <a href="/sql">SQL</a>
41
+ </span>
42
+ </span>
43
+ <span className="menu-link">
44
+ <span className="menu-title">
45
+ <a
46
+ href="https://www.datajunction.io"
47
+ target="_blank"
48
+ rel="noreferrer"
49
+ >
50
+ Docs
51
+ </a>
33
52
  </span>
34
53
  </span>
35
54
  </div>
36
55
  </div>
37
56
  </div>
57
+ {process.env.REACT_DISABLE_AUTH === 'true' ? (
58
+ ''
59
+ ) : (
60
+ <span className="menu-link">
61
+ <span className="menu-title">
62
+ <button onClick={handleLogout}>Logout</button>
63
+ </span>
64
+ </span>
65
+ )}
38
66
  </div>
39
67
  <Outlet />
40
68
  </>
@@ -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 SQLBuilderPage = props => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.SQLBuilderPage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )(props);
16
+ };