datajunction-ui 0.0.1-rc.9 → 0.0.3

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 (205) hide show
  1. package/.env +2 -0
  2. package/.prettierignore +3 -1
  3. package/Makefile +9 -0
  4. package/dj-logo.svg +10 -0
  5. package/package.json +53 -14
  6. package/public/favicon.ico +0 -0
  7. package/public/index.html +1 -1
  8. package/src/__tests__/reportWebVitals.test.jsx +44 -0
  9. package/src/app/__tests__/__snapshots__/index.test.tsx.snap +5 -109
  10. package/src/app/components/AddNodeDropdown.jsx +44 -0
  11. package/src/app/components/ListGroupItem.jsx +9 -1
  12. package/src/app/components/NamespaceHeader.jsx +4 -13
  13. package/src/app/components/NodeListActions.jsx +69 -0
  14. package/src/app/components/NodeMaterializationDelete.jsx +90 -0
  15. package/src/app/components/QueryInfo.jsx +172 -0
  16. package/src/app/components/Search.jsx +94 -0
  17. package/src/app/components/Tab.jsx +8 -1
  18. package/src/app/components/ToggleSwitch.jsx +20 -0
  19. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  20. package/src/app/components/__tests__/QueryInfo.test.jsx +55 -0
  21. package/src/app/components/__tests__/Search.test.jsx +63 -0
  22. package/src/app/components/__tests__/Tab.test.jsx +27 -0
  23. package/src/app/components/__tests__/ToggleSwitch.test.jsx +43 -0
  24. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +8 -3
  25. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -18
  26. package/src/app/components/djgraph/Collapse.jsx +47 -0
  27. package/src/app/components/djgraph/DJNode.jsx +61 -83
  28. package/src/app/components/djgraph/DJNodeColumns.jsx +75 -0
  29. package/src/app/components/djgraph/DJNodeDimensions.jsx +75 -0
  30. package/src/app/components/djgraph/LayoutFlow.jsx +106 -0
  31. package/src/app/components/djgraph/__tests__/Collapse.test.jsx +51 -0
  32. package/src/app/components/djgraph/__tests__/DJNodeColumns.test.jsx +83 -0
  33. package/src/app/components/djgraph/__tests__/DJNodeDimensions.test.jsx +118 -0
  34. package/src/app/components/djgraph/__tests__/__snapshots__/DJNode.test.tsx.snap +84 -40
  35. package/src/app/components/forms/Action.jsx +8 -0
  36. package/src/app/components/forms/NodeNameField.jsx +64 -0
  37. package/src/app/components/search.css +17 -0
  38. package/src/app/constants.js +2 -0
  39. package/src/app/icons/AddItemIcon.jsx +16 -0
  40. package/src/app/icons/AlertIcon.jsx +33 -0
  41. package/src/app/icons/CollapsedIcon.jsx +15 -0
  42. package/src/app/icons/CommitIcon.jsx +45 -0
  43. package/src/app/icons/DJLogo.jsx +36 -0
  44. package/src/app/icons/DeleteIcon.jsx +21 -0
  45. package/src/app/icons/DiffIcon.jsx +63 -0
  46. package/src/app/icons/EditIcon.jsx +18 -0
  47. package/src/app/icons/ExpandedIcon.jsx +15 -0
  48. package/src/app/icons/EyeIcon.jsx +20 -0
  49. package/src/app/icons/FilterIcon.jsx +7 -0
  50. package/src/app/icons/HorizontalHierarchyIcon.jsx +15 -0
  51. package/src/app/icons/InvalidIcon.jsx +16 -0
  52. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  53. package/src/app/icons/LoadingIcon.jsx +14 -0
  54. package/src/app/icons/NodeIcon.jsx +49 -0
  55. package/src/app/icons/PythonIcon.jsx +14 -0
  56. package/src/app/icons/TableIcon.jsx +14 -0
  57. package/src/app/icons/ValidIcon.jsx +16 -0
  58. package/src/app/index.tsx +118 -37
  59. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  60. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  61. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  62. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  63. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +51 -0
  64. package/src/app/pages/AddEditNodePage/FullNameField.jsx +38 -0
  65. package/src/app/pages/AddEditNodePage/Loadable.jsx +20 -0
  66. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  67. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  68. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  69. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  70. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +94 -0
  71. package/src/app/pages/AddEditNodePage/OwnersField.jsx +54 -0
  72. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  73. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  74. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  75. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +109 -0
  76. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +287 -0
  77. package/src/app/pages/AddEditNodePage/__tests__/FormikSelect.test.jsx +75 -0
  78. package/src/app/pages/AddEditNodePage/__tests__/FullNameField.test.jsx +31 -0
  79. package/src/app/pages/AddEditNodePage/__tests__/NodeQueryField.test.jsx +30 -0
  80. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +54 -0
  81. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +3 -0
  82. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/index.test.jsx.snap +3 -0
  83. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +224 -0
  84. package/src/app/pages/AddEditNodePage/index.jsx +506 -0
  85. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  86. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  87. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  88. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +152 -0
  89. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  90. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +75 -0
  91. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +373 -0
  92. package/src/app/pages/CubeBuilderPage/index.jsx +291 -0
  93. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  94. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  95. package/src/app/pages/LoginPage/__tests__/index.test.jsx +97 -0
  96. package/src/app/pages/LoginPage/assets/sign-in-with-github.png +0 -0
  97. package/src/app/pages/LoginPage/assets/sign-in-with-google.png +0 -0
  98. package/src/app/pages/LoginPage/index.jsx +17 -0
  99. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +85 -0
  100. package/src/app/pages/NamespacePage/Explorer.jsx +61 -0
  101. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  102. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  103. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  104. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  105. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +297 -0
  106. package/src/app/pages/NamespacePage/index.jsx +319 -42
  107. package/src/app/pages/NodePage/AddBackfillPopover.jsx +165 -0
  108. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +222 -0
  109. package/src/app/pages/NodePage/AvailabilityStateBlock.jsx +67 -0
  110. package/src/app/pages/NodePage/ClientCodePopover.jsx +94 -0
  111. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  112. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  113. package/src/app/pages/NodePage/EditColumnPopover.jsx +116 -0
  114. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +164 -0
  115. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  116. package/src/app/pages/NodePage/NodeColumnTab.jsx +256 -30
  117. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  118. package/src/app/pages/NodePage/NodeGraphTab.jsx +119 -148
  119. package/src/app/pages/NodePage/NodeHistory.jsx +236 -0
  120. package/src/app/pages/NodePage/NodeInfoTab.jsx +325 -49
  121. package/src/app/pages/NodePage/NodeLineageTab.jsx +84 -0
  122. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +585 -0
  123. package/src/app/pages/NodePage/NodeRevisionMaterializationTab.jsx +58 -0
  124. package/src/app/pages/NodePage/NodeStatus.jsx +100 -31
  125. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  126. package/src/app/pages/NodePage/NodesWithDimension.jsx +42 -0
  127. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  128. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +151 -0
  129. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  130. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  131. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  132. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +56 -0
  133. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  134. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  135. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  136. package/src/app/pages/NodePage/__tests__/EditColumnPopover.test.jsx +148 -0
  137. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +161 -0
  138. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  139. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  140. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +595 -0
  141. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +58 -0
  142. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +190 -0
  143. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +872 -0
  144. package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +175 -0
  145. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  146. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +19 -0
  147. package/src/app/pages/NodePage/index.jsx +190 -44
  148. package/src/app/pages/NotFoundPage/__tests__/index.test.jsx +16 -0
  149. package/src/app/pages/OverviewPage/ByStatusPanel.jsx +69 -0
  150. package/src/app/pages/OverviewPage/DimensionNodeUsagePanel.jsx +48 -0
  151. package/src/app/pages/OverviewPage/GovernanceWarningsPanel.jsx +107 -0
  152. package/src/app/pages/OverviewPage/Loadable.jsx +16 -0
  153. package/src/app/pages/OverviewPage/NodesByTypePanel.jsx +63 -0
  154. package/src/app/pages/OverviewPage/OverviewPanel.jsx +94 -0
  155. package/src/app/pages/OverviewPage/TrendsPanel.jsx +66 -0
  156. package/src/app/pages/OverviewPage/__tests__/ByStatusPanel.test.jsx +36 -0
  157. package/src/app/pages/OverviewPage/__tests__/DimensionNodeUsagePanel.test.jsx +76 -0
  158. package/src/app/pages/OverviewPage/__tests__/GovernanceWarningsPanel.test.jsx +77 -0
  159. package/src/app/pages/OverviewPage/__tests__/NodesByTypePanel.test.jsx +86 -0
  160. package/src/app/pages/OverviewPage/__tests__/OverviewPanel.test.jsx +78 -0
  161. package/src/app/pages/OverviewPage/__tests__/TrendsPanel.test.jsx +120 -0
  162. package/src/app/pages/OverviewPage/__tests__/index.test.jsx +54 -0
  163. package/src/app/pages/OverviewPage/index.jsx +22 -0
  164. package/src/app/pages/RegisterTablePage/Loadable.jsx +16 -0
  165. package/src/app/pages/RegisterTablePage/__tests__/RegisterTablePage.test.jsx +110 -0
  166. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +38 -0
  167. package/src/app/pages/RegisterTablePage/index.jsx +142 -0
  168. package/src/app/pages/Root/__tests__/index.test.jsx +79 -0
  169. package/src/app/pages/Root/index.tsx +84 -6
  170. package/src/app/pages/SQLBuilderPage/Loadable.jsx +16 -0
  171. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +173 -0
  172. package/src/app/pages/SQLBuilderPage/index.jsx +390 -0
  173. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  174. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  175. package/src/app/pages/TagPage/index.jsx +79 -0
  176. package/src/app/services/DJService.js +1314 -21
  177. package/src/app/services/__tests__/DJService.test.jsx +1559 -0
  178. package/src/index.tsx +1 -0
  179. package/src/mocks/mockNodes.jsx +1474 -0
  180. package/src/setupTests.ts +31 -1
  181. package/src/styles/dag.css +117 -5
  182. package/src/styles/index.css +1027 -30
  183. package/src/styles/loading.css +34 -0
  184. package/src/styles/login.css +81 -0
  185. package/src/styles/node-creation.scss +276 -0
  186. package/src/styles/node-list.css +4 -0
  187. package/src/styles/overview.css +72 -0
  188. package/src/styles/sorted-table.css +15 -0
  189. package/src/styles/styles.scss +44 -0
  190. package/src/styles/styles.scss.d.ts +9 -0
  191. package/src/utils/form.jsx +23 -0
  192. package/webpack.config.js +16 -6
  193. package/.babelrc +0 -4
  194. package/.env.local +0 -4
  195. package/.env.production +0 -1
  196. package/.github/pull_request_template.md +0 -11
  197. package/.github/workflows/ci.yml +0 -33
  198. package/.vscode/extensions.json +0 -7
  199. package/.vscode/launch.json +0 -15
  200. package/.vscode/settings.json +0 -25
  201. package/Dockerfile +0 -7
  202. package/src/app/pages/ListNamespacesPage/Loadable.jsx +0 -14
  203. package/src/app/pages/ListNamespacesPage/index.jsx +0 -62
  204. package/src/app/pages/NamespacePage/__tests__/__snapshots__/index.test.tsx.snap +0 -45
  205. package/src/app/pages/NamespacePage/__tests__/index.test.tsx +0 -14
@@ -0,0 +1,124 @@
1
+ import { useState } from 'react';
2
+ import { Formik, Form, Field, ErrorMessage } from 'formik';
3
+ import '../../../styles/login.css';
4
+ import LoadingIcon from '../../icons/LoadingIcon';
5
+ import logo from '../Root/assets/dj-logo.png';
6
+ import GitHubLoginButton from './assets/sign-in-with-github.png';
7
+ import GoogleLoginButton from './assets/sign-in-with-google.png';
8
+ import * as Yup from 'yup';
9
+
10
+ const githubLoginURL = new URL('/github/login/', process.env.REACT_APP_DJ_URL);
11
+ const googleLoginURL = new URL('/google/login/', process.env.REACT_APP_DJ_URL);
12
+
13
+ const LoginSchema = Yup.object().shape({
14
+ username: Yup.string()
15
+ .min(2, 'Must be at least 2 characters')
16
+ .required('Username is required'),
17
+ password: Yup.string().required('Password is required'),
18
+ });
19
+
20
+ export default function LoginForm({ setShowSignup }) {
21
+ const [, setError] = useState('');
22
+
23
+ // Add the path that the user was trying to access in order to properly redirect after auth
24
+ githubLoginURL.searchParams.append('target', window.location.pathname);
25
+ googleLoginURL.searchParams.append('target', window.location.pathname);
26
+
27
+ const handleBasicLogin = async ({ username, password }) => {
28
+ const data = new FormData();
29
+ data.append('username', username);
30
+ data.append('password', password);
31
+ await fetch(`${process.env.REACT_APP_DJ_URL}/basic/login/`, {
32
+ method: 'POST',
33
+ body: data,
34
+ credentials: 'include',
35
+ }).catch(error => {
36
+ setError(error ? JSON.stringify(error) : '');
37
+ });
38
+ window.location.reload();
39
+ };
40
+
41
+ return (
42
+ <Formik
43
+ initialValues={{
44
+ username: '',
45
+ password: '',
46
+ target: window.location.pathname,
47
+ }}
48
+ validationSchema={LoginSchema}
49
+ onSubmit={(values, { setSubmitting }) => {
50
+ setTimeout(() => {
51
+ handleBasicLogin(values);
52
+ setSubmitting(false);
53
+ }, 400);
54
+ }}
55
+ >
56
+ {({ isSubmitting }) => (
57
+ <Form>
58
+ <div className="logo-title">
59
+ <img src={logo} alt="DJ Logo" width="75px" height="75px" />
60
+ <h2>DataJunction</h2>
61
+ </div>
62
+ <div>
63
+ <Field type="text" name="username" placeholder="Username" />
64
+ </div>
65
+ <div>
66
+ <ErrorMessage
67
+ className="form-error"
68
+ name="username"
69
+ component="span"
70
+ />
71
+ </div>
72
+ <div>
73
+ <Field type="password" name="password" placeholder="Password" />
74
+ </div>
75
+ <div>
76
+ <ErrorMessage
77
+ className="form-error"
78
+ name="password"
79
+ component="span"
80
+ />
81
+ </div>
82
+ <div>
83
+ <p>
84
+ Don't have an account yet?{' '}
85
+ <a onClick={() => setShowSignup(true)}>Sign Up</a>
86
+ </p>
87
+ </div>
88
+ <button type="submit" disabled={isSubmitting}>
89
+ {isSubmitting ? <LoadingIcon /> : 'Login'}
90
+ </button>
91
+ <div>
92
+ <p>Or</p>
93
+ </div>
94
+ {process.env.REACT_ENABLE_GITHUB_OAUTH === 'true' ? (
95
+ <div>
96
+ <a href={githubLoginURL.href}>
97
+ <img
98
+ src={GitHubLoginButton}
99
+ alt="Sign in with GitHub"
100
+ width="200px"
101
+ />
102
+ </a>
103
+ </div>
104
+ ) : (
105
+ ''
106
+ )}
107
+ {process.env.REACT_ENABLE_GOOGLE_OAUTH === 'true' ? (
108
+ <div>
109
+ <a href={googleLoginURL.href}>
110
+ <img
111
+ src={GoogleLoginButton}
112
+ alt="Sign in with Google"
113
+ width="200px"
114
+ />
115
+ </a>
116
+ </div>
117
+ ) : (
118
+ ''
119
+ )}
120
+ </Form>
121
+ )}
122
+ </Formik>
123
+ );
124
+ }
@@ -0,0 +1,156 @@
1
+ import { useState } from 'react';
2
+ import { Formik, Form, Field, ErrorMessage } from 'formik';
3
+ import '../../../styles/login.css';
4
+ import logo from '../Root/assets/dj-logo.png';
5
+ import LoadingIcon from '../../icons/LoadingIcon';
6
+ import GitHubLoginButton from './assets/sign-in-with-github.png';
7
+ import GoogleLoginButton from './assets/sign-in-with-google.png';
8
+ import * as Yup from 'yup';
9
+
10
+ const githubLoginURL = new URL('/github/login/', process.env.REACT_APP_DJ_URL);
11
+ const googleLoginURL = new URL('/google/login/', process.env.REACT_APP_DJ_URL);
12
+
13
+ const SignupSchema = Yup.object().shape({
14
+ email: Yup.string().email('Invalid email').required('Email is required'),
15
+ signupUsername: Yup.string()
16
+ .min(3, 'Must be at least 2 characters')
17
+ .max(20, 'Must be less than 20 characters')
18
+ .required('Username is required'),
19
+ signupPassword: Yup.string().required('Password is required'),
20
+ });
21
+
22
+ export default function SignupForm({ setShowSignup }) {
23
+ const [, setError] = useState('');
24
+
25
+ // Add the path that the user was trying to access in order to properly redirect after auth
26
+ githubLoginURL.searchParams.append('target', window.location.pathname);
27
+ googleLoginURL.searchParams.append('target', window.location.pathname);
28
+
29
+ const handleBasicSignup = async ({
30
+ email,
31
+ signupUsername,
32
+ signupPassword,
33
+ }) => {
34
+ const data = new FormData();
35
+ data.append('email', email);
36
+ data.append('username', signupUsername);
37
+ data.append('password', signupPassword);
38
+ await fetch(`${process.env.REACT_APP_DJ_URL}/basic/user/`, {
39
+ method: 'POST',
40
+ body: data,
41
+ credentials: 'include',
42
+ }).catch(error => {
43
+ setError(error ? JSON.stringify(error) : '');
44
+ });
45
+ const loginData = new FormData();
46
+ loginData.append('username', signupUsername);
47
+ loginData.append('password', signupPassword);
48
+ await fetch(`${process.env.REACT_APP_DJ_URL}/basic/login/`, {
49
+ method: 'POST',
50
+ body: data,
51
+ credentials: 'include',
52
+ }).catch(error => {
53
+ setError(error ? JSON.stringify(error) : '');
54
+ });
55
+ window.location.reload();
56
+ };
57
+
58
+ return (
59
+ <Formik
60
+ initialValues={{
61
+ email: '',
62
+ signupUsername: '',
63
+ signupPassword: '',
64
+ target: window.location.pathname,
65
+ }}
66
+ validationSchema={SignupSchema}
67
+ onSubmit={(values, { setSubmitting }) => {
68
+ setTimeout(() => {
69
+ handleBasicSignup(values);
70
+ setSubmitting(false);
71
+ }, 400);
72
+ }}
73
+ >
74
+ {({ isSubmitting }) => (
75
+ <Form>
76
+ <div className="logo-title">
77
+ <img src={logo} alt="DJ Logo" width="75px" height="75px" />
78
+ <h2>DataJunction</h2>
79
+ </div>
80
+ <div>
81
+ <Field type="text" name="email" placeholder="Email" />
82
+ </div>
83
+ <div>
84
+ <ErrorMessage
85
+ className="form-error"
86
+ name="email"
87
+ component="span"
88
+ />
89
+ </div>
90
+ <div>
91
+ <Field type="text" name="signupUsername" placeholder="Username" />
92
+ </div>
93
+ <div>
94
+ <ErrorMessage
95
+ className="form-error"
96
+ name="signupUsername"
97
+ component="span"
98
+ />
99
+ </div>
100
+ <div>
101
+ <Field
102
+ type="password"
103
+ name="signupPassword"
104
+ placeholder="Password"
105
+ />
106
+ </div>
107
+ <div>
108
+ <ErrorMessage
109
+ className="form-error"
110
+ name="signupPassword"
111
+ component="span"
112
+ />
113
+ </div>
114
+ <div>
115
+ <p>
116
+ Have an account already?{' '}
117
+ <a onClick={() => setShowSignup(false)}>Login</a>
118
+ </p>
119
+ </div>
120
+ <button type="submit" disabled={isSubmitting}>
121
+ {isSubmitting ? <LoadingIcon /> : 'Sign Up'}
122
+ </button>
123
+ <div>
124
+ <p>Or</p>
125
+ </div>
126
+ {process.env.REACT_ENABLE_GITHUB_OAUTH === 'true' ? (
127
+ <div>
128
+ <a href={githubLoginURL.href}>
129
+ <img
130
+ src={GitHubLoginButton}
131
+ alt="Sign in with GitHub"
132
+ width="200px"
133
+ />
134
+ </a>
135
+ </div>
136
+ ) : (
137
+ ''
138
+ )}
139
+ {process.env.REACT_ENABLE_GOOGLE_OAUTH === 'true' ? (
140
+ <div>
141
+ <a href={googleLoginURL.href}>
142
+ <img
143
+ src={GoogleLoginButton}
144
+ alt="Sign in with Google"
145
+ width="200px"
146
+ />
147
+ </a>
148
+ </div>
149
+ ) : (
150
+ ''
151
+ )}
152
+ </Form>
153
+ )}
154
+ </Formik>
155
+ );
156
+ }
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, waitFor } from '@testing-library/react';
3
+ import { LoginPage } from '../index';
4
+
5
+ describe('LoginPage', () => {
6
+ const original = window.location;
7
+
8
+ beforeAll(() => {
9
+ Object.defineProperty(window, 'location', {
10
+ configurable: true,
11
+ value: { reload: jest.fn() },
12
+ });
13
+ });
14
+
15
+ afterAll(() => {
16
+ Object.defineProperty(window, 'location', {
17
+ configurable: true,
18
+ value: original,
19
+ });
20
+ });
21
+
22
+ beforeEach(() => {
23
+ fetch.resetMocks();
24
+ });
25
+
26
+ afterEach(() => {
27
+ jest.clearAllMocks();
28
+ });
29
+
30
+ it('displays error messages when fields are empty and form is submitted', async () => {
31
+ const { getByText, queryAllByText } = render(<LoginPage />);
32
+ fireEvent.click(getByText('Login'));
33
+
34
+ await waitFor(() => {
35
+ expect(getByText('DataJunction')).toBeInTheDocument();
36
+ expect(getByText('Username is required')).toBeInTheDocument();
37
+ expect(getByText('Password is required')).toBeInTheDocument();
38
+ });
39
+ });
40
+
41
+ it('calls fetch with correct data on login', async () => {
42
+ const username = 'testUser';
43
+ const password = 'testPassword';
44
+
45
+ const { getByText, getByPlaceholderText } = render(<LoginPage />);
46
+ fireEvent.change(getByPlaceholderText('Username'), {
47
+ target: { value: username },
48
+ });
49
+ fireEvent.change(getByPlaceholderText('Password'), {
50
+ target: { value: password },
51
+ });
52
+ fireEvent.click(getByText('Login'));
53
+
54
+ await waitFor(() => {
55
+ expect(fetch).toHaveBeenCalledWith(
56
+ `${process.env.REACT_APP_DJ_URL}/basic/login/`,
57
+ expect.objectContaining({
58
+ method: 'POST',
59
+ body: expect.any(FormData),
60
+ credentials: 'include',
61
+ }),
62
+ );
63
+ expect(window.location.reload).toHaveBeenCalled();
64
+ });
65
+ });
66
+
67
+ it('calls fetch with correct data on signup', async () => {
68
+ const email = 'testEmail@testEmail.com';
69
+ const username = 'testUser';
70
+ const password = 'testPassword';
71
+
72
+ const { getByText, getByPlaceholderText } = render(<LoginPage />);
73
+ fireEvent.click(getByText('Sign Up'));
74
+ fireEvent.change(getByPlaceholderText('Email'), {
75
+ target: { value: email },
76
+ });
77
+ fireEvent.change(getByPlaceholderText('Username'), {
78
+ target: { value: username },
79
+ });
80
+ fireEvent.change(getByPlaceholderText('Password'), {
81
+ target: { value: password },
82
+ });
83
+ fireEvent.click(getByText('Sign Up'));
84
+
85
+ await waitFor(() => {
86
+ expect(fetch).toHaveBeenCalledWith(
87
+ `${process.env.REACT_APP_DJ_URL}/basic/user/`,
88
+ expect.objectContaining({
89
+ method: 'POST',
90
+ body: expect.any(FormData),
91
+ credentials: 'include',
92
+ }),
93
+ );
94
+ expect(window.location.reload).toHaveBeenCalled();
95
+ });
96
+ });
97
+ });
@@ -0,0 +1,17 @@
1
+ import { useState } from 'react';
2
+ import '../../../styles/login.css';
3
+ import SignupForm from './SignupForm';
4
+ import LoginForm from './LoginForm';
5
+
6
+ export function LoginPage() {
7
+ const [showSignup, setShowSignup] = useState(false);
8
+ return (
9
+ <div className="container login">
10
+ {showSignup ? (
11
+ <SignupForm setShowSignup={setShowSignup} />
12
+ ) : (
13
+ <LoginForm setShowSignup={setShowSignup} />
14
+ )}
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,85 @@
1
+ import { useContext, 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 AddItemIcon from '../../icons/AddItemIcon';
6
+ import { displayMessageAfterSubmit } from '../../../utils/form';
7
+
8
+ export default function AddNamespacePopover({ namespace }) {
9
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
10
+ const [popoverAnchor, setPopoverAnchor] = useState(false);
11
+
12
+ const addNamespace = async ({ namespace }, { setSubmitting, setStatus }) => {
13
+ setSubmitting(false);
14
+ const response = await djClient.addNamespace(namespace);
15
+ if (response.status === 200 || response.status === 201) {
16
+ setStatus({ success: 'Saved' });
17
+ } else {
18
+ setStatus({
19
+ failure: `${response.json.message}`,
20
+ });
21
+ }
22
+ window.location.reload();
23
+ };
24
+
25
+ return (
26
+ <>
27
+ <button
28
+ className="edit_button"
29
+ aria-label="AddNamespaceTogglePopover"
30
+ tabIndex="0"
31
+ onClick={() => {
32
+ setPopoverAnchor(!popoverAnchor);
33
+ }}
34
+ >
35
+ <AddItemIcon />
36
+ </button>
37
+ <div
38
+ className="popover"
39
+ role="dialog"
40
+ aria-label="AddNamespacePopover"
41
+ style={{
42
+ display: popoverAnchor === false ? 'none' : 'block',
43
+ width: '200px !important',
44
+ textTransform: 'none',
45
+ fontWeight: 'normal',
46
+ }}
47
+ >
48
+ <Formik
49
+ initialValues={{
50
+ namespace: namespace + '.',
51
+ }}
52
+ onSubmit={addNamespace}
53
+ >
54
+ {function Render({ isSubmitting, status, setFieldValue }) {
55
+ return (
56
+ <Form>
57
+ {displayMessageAfterSubmit(status)}
58
+ <span data-testid="add-namespace">
59
+ <ErrorMessage name="namespace" component="span" />
60
+ <label htmlFor="namespace">Namespace</label>
61
+ <Field
62
+ type="text"
63
+ name="namespace"
64
+ id="namespace"
65
+ placeholder="New namespace"
66
+ default={namespace}
67
+ />
68
+ </span>
69
+ <button
70
+ className="add_node"
71
+ type="submit"
72
+ aria-label="SaveNamespace"
73
+ aria-hidden="false"
74
+ style={{ marginTop: '1rem' }}
75
+ >
76
+ Save
77
+ </button>
78
+ </Form>
79
+ );
80
+ }}
81
+ </Formik>
82
+ </div>
83
+ </>
84
+ );
85
+ }
@@ -0,0 +1,61 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import CollapsedIcon from '../../icons/CollapsedIcon';
3
+ import ExpandedIcon from '../../icons/ExpandedIcon';
4
+
5
+ const Explorer = ({ item = [], current }) => {
6
+ const [items, setItems] = useState([]);
7
+ const [expand, setExpand] = useState(false);
8
+ const [highlight, setHighlight] = useState(false);
9
+
10
+ useEffect(() => {
11
+ setItems(item);
12
+ setHighlight(current);
13
+ if (current !== undefined && current?.startsWith(item.path)) {
14
+ setExpand(true);
15
+ } else setExpand(false);
16
+ }, [current, item]);
17
+
18
+ const handleClickOnParent = e => {
19
+ e.stopPropagation();
20
+ setExpand(prev => {
21
+ return !prev;
22
+ });
23
+ };
24
+
25
+ return (
26
+ <>
27
+ <div
28
+ className={`select-name ${
29
+ highlight === items.path ? 'select-name-highlight' : ''
30
+ }`}
31
+ onClick={handleClickOnParent}
32
+ >
33
+ {items.children && items.children.length > 0 ? (
34
+ <span>{!expand ? <CollapsedIcon /> : <ExpandedIcon />} </span>
35
+ ) : null}
36
+ <a href={`/namespaces/${items.path}`}>{items.namespace}</a>{' '}
37
+ </div>
38
+ {items.children
39
+ ? items.children.map((item, index) => (
40
+ <div
41
+ style={{
42
+ paddingLeft: '1.4rem',
43
+ marginLeft: '1rem',
44
+ borderLeft: '1px solid rgb(218 233 255)',
45
+ }}
46
+ key={index}
47
+ >
48
+ <div
49
+ className={`${expand ? '' : 'inactive'}`}
50
+ key={`nested-${index}`}
51
+ >
52
+ <Explorer item={item} current={highlight} />
53
+ </div>
54
+ </div>
55
+ ))
56
+ : null}
57
+ </>
58
+ );
59
+ };
60
+
61
+ export default Explorer;
@@ -0,0 +1,21 @@
1
+ import { components } from 'react-select';
2
+
3
+ const Control = ({ children, ...props }) => {
4
+ const { label, onLabelClick } = props.selectProps;
5
+ const style = {
6
+ cursor: 'pointer',
7
+ padding: '10px 5px 10px 12px',
8
+ color: 'rgb(112, 110, 115)',
9
+ };
10
+
11
+ return (
12
+ <components.Control {...props}>
13
+ <span onMouseDown={onLabelClick} style={style}>
14
+ {label}
15
+ </span>
16
+ {children}
17
+ </components.Control>
18
+ );
19
+ };
20
+
21
+ export default Control;
@@ -0,0 +1,30 @@
1
+ import Select from 'react-select';
2
+ import Control from './FieldControl';
3
+
4
+ export default function NodeTypeSelect({ onChange }) {
5
+ return (
6
+ <span
7
+ className="menu-link"
8
+ style={{ marginLeft: '30px', width: '300px' }}
9
+ data-testid="select-node-type"
10
+ >
11
+ <Select
12
+ name="node_type"
13
+ isClearable
14
+ label="Node Type"
15
+ components={{ Control }}
16
+ onChange={e => onChange(e)}
17
+ styles={{
18
+ control: styles => ({ ...styles, backgroundColor: 'white' }),
19
+ }}
20
+ options={[
21
+ { value: 'source', label: 'Source' },
22
+ { value: 'transform', label: 'Transform' },
23
+ { value: 'dimension', label: 'Dimension' },
24
+ { value: 'metric', label: 'Metric' },
25
+ { value: 'cube', label: 'Cube' },
26
+ ]}
27
+ />
28
+ </span>
29
+ );
30
+ }
@@ -0,0 +1,44 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+ import Control from './FieldControl';
4
+
5
+ import Select from 'react-select';
6
+
7
+ export default function TagSelect({ onChange }) {
8
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
9
+
10
+ const [retrieved, setRetrieved] = useState(false);
11
+ const [tags, setTags] = useState([]);
12
+
13
+ useEffect(() => {
14
+ const fetchData = async () => {
15
+ const tags = await djClient.listTags();
16
+ setTags(tags);
17
+ setRetrieved(true);
18
+ };
19
+ fetchData().catch(console.error);
20
+ }, [djClient]);
21
+
22
+ return (
23
+ <span
24
+ className="menu-link"
25
+ style={{ marginLeft: '30px', width: '350px' }}
26
+ data-testid="select-tag"
27
+ >
28
+ <Select
29
+ name="tags"
30
+ isClearable
31
+ isMulti
32
+ label="Tags"
33
+ components={{ Control }}
34
+ onChange={e => onChange(e)}
35
+ options={tags?.map(tag => {
36
+ return {
37
+ value: tag.name,
38
+ label: tag.display_name,
39
+ };
40
+ })}
41
+ />
42
+ </span>
43
+ );
44
+ }
@@ -0,0 +1,47 @@
1
+ import { useContext, useEffect, useState } from 'react';
2
+ import DJClientContext from '../../providers/djclient';
3
+ import Control from './FieldControl';
4
+
5
+ import Select from 'react-select';
6
+
7
+ export default function UserSelect({ onChange, currentUser }) {
8
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
9
+ const [retrieved, setRetrieved] = useState(false);
10
+ const [users, setUsers] = useState([]);
11
+
12
+ useEffect(() => {
13
+ const fetchData = async () => {
14
+ const users = await djClient.users();
15
+ setUsers(users);
16
+ setRetrieved(true);
17
+ };
18
+ fetchData().catch(console.error);
19
+ }, [djClient]);
20
+
21
+ return (
22
+ <span
23
+ className="menu-link"
24
+ style={{ marginLeft: '30px', width: '400px' }}
25
+ data-testid="select-user"
26
+ >
27
+ {retrieved ? (
28
+ <Select
29
+ name="edited_by"
30
+ isClearable
31
+ label="Edited By"
32
+ components={{ Control }}
33
+ onChange={e => onChange(e)}
34
+ defaultValue={{
35
+ value: currentUser,
36
+ label: currentUser,
37
+ }}
38
+ options={users?.map(user => {
39
+ return { value: user.username, label: user.username };
40
+ })}
41
+ />
42
+ ) : (
43
+ ''
44
+ )}
45
+ </span>
46
+ );
47
+ }