datajunction-ui 0.0.1-rc.23 → 0.0.1-rc.25

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 (38) hide show
  1. package/.env +1 -0
  2. package/package.json +3 -2
  3. package/src/app/components/Tab.jsx +0 -1
  4. package/src/app/constants.js +2 -2
  5. package/src/app/icons/LoadingIcon.jsx +14 -0
  6. package/src/app/index.tsx +11 -1
  7. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +28 -2
  8. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +44 -9
  9. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +1 -0
  10. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +0 -50
  11. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +2 -0
  12. package/src/app/pages/AddEditNodePage/index.jsx +60 -6
  13. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  14. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  15. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  16. package/src/app/pages/LoginPage/LoginForm.jsx +116 -0
  17. package/src/app/pages/LoginPage/SignupForm.jsx +144 -0
  18. package/src/app/pages/LoginPage/__tests__/index.test.jsx +34 -2
  19. package/src/app/pages/LoginPage/index.jsx +9 -82
  20. package/src/app/pages/NamespacePage/index.jsx +5 -0
  21. package/src/app/pages/NodePage/ClientCodePopover.jsx +15 -1
  22. package/src/app/pages/NodePage/EditColumnPopover.jsx +15 -1
  23. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +16 -1
  24. package/src/app/pages/NodePage/NodeColumnTab.jsx +11 -0
  25. package/src/app/pages/NodePage/NodeInfoTab.jsx +5 -1
  26. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +6 -3
  27. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +1 -0
  28. package/src/app/pages/Root/index.tsx +1 -1
  29. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  30. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  31. package/src/app/pages/TagPage/index.jsx +79 -0
  32. package/src/app/services/DJService.js +79 -1
  33. package/src/app/services/__tests__/DJService.test.jsx +84 -1
  34. package/src/mocks/mockNodes.jsx +88 -44
  35. package/src/styles/index.css +19 -0
  36. package/src/styles/loading.css +35 -0
  37. package/src/styles/login.css +17 -3
  38. package/src/utils/form.jsx +2 -2
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Add or edit tags
3
+ */
4
+ import { ErrorMessage, Field, Form, Formik } from 'formik';
5
+
6
+ import NamespaceHeader from '../../components/NamespaceHeader';
7
+ import React, { useContext } from 'react';
8
+ import DJClientContext from '../../providers/djclient';
9
+ import 'styles/node-creation.scss';
10
+ import { displayMessageAfterSubmit } from '../../../utils/form';
11
+
12
+ export function AddEditTagPage() {
13
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
14
+ const initialValues = {
15
+ name: '',
16
+ };
17
+
18
+ const validator = values => {
19
+ const errors = {};
20
+ if (!values.name) {
21
+ errors.name = 'Required';
22
+ }
23
+ return errors;
24
+ };
25
+
26
+ const handleSubmit = async (values, { setSubmitting, setStatus }) => {
27
+ const { status, json } = await djClient.addTag(
28
+ values.name,
29
+ values.display_name,
30
+ values.tag_type,
31
+ values.description,
32
+ );
33
+ if (status === 200 || status === 201) {
34
+ setStatus({
35
+ success: (
36
+ <>
37
+ Successfully added tag{' '}
38
+ <a href={`/tags/${json.name}`}>{json.display_name}</a>.
39
+ </>
40
+ ),
41
+ });
42
+ } else {
43
+ setStatus({
44
+ failure: `${json.message}`,
45
+ });
46
+ }
47
+ setSubmitting(false);
48
+ window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
49
+ };
50
+
51
+ return (
52
+ <div className="mid">
53
+ <NamespaceHeader namespace="" />
54
+ <div className="card">
55
+ <div className="card-header">
56
+ <h2>
57
+ Add{' '}
58
+ <span className={`node_type__source node_type_creation_heading`}>
59
+ Tag
60
+ </span>
61
+ </h2>
62
+ <center>
63
+ <Formik
64
+ initialValues={initialValues}
65
+ validate={validator}
66
+ onSubmit={handleSubmit}
67
+ >
68
+ {function Render({ isSubmitting, status }) {
69
+ return (
70
+ <Form>
71
+ {displayMessageAfterSubmit(status)}
72
+ {
73
+ <>
74
+ <div className="NodeCreationInput">
75
+ <ErrorMessage name="name" component="span" />
76
+ <label htmlFor="name">Name</label>
77
+ <Field
78
+ type="text"
79
+ name="name"
80
+ id="name"
81
+ placeholder="Tag Name"
82
+ />
83
+ </div>
84
+ <br />
85
+ <div className="FullNameInput NodeCreationInput">
86
+ <ErrorMessage name="display_name" component="span" />
87
+ <label htmlFor="display_name">Display Name</label>
88
+ <Field
89
+ type="text"
90
+ name="display_name"
91
+ id="display_name"
92
+ placeholder="Display Name"
93
+ class="FullNameField"
94
+ />
95
+ </div>
96
+ <br />
97
+ <div className="NodeCreationInput">
98
+ <ErrorMessage name="tag_type" component="span" />
99
+ <label htmlFor="tag_type">Tag Type</label>
100
+ <Field
101
+ type="text"
102
+ name="tag_type"
103
+ id="tag_type"
104
+ placeholder="Tag Type"
105
+ />
106
+ </div>
107
+ <div className="DescriptionInput NodeCreationInput">
108
+ <ErrorMessage name="description" component="span" />
109
+ <label htmlFor="description">Description</label>
110
+ <Field
111
+ type="textarea"
112
+ as="textarea"
113
+ name="description"
114
+ id="Description"
115
+ placeholder="Describe the tag"
116
+ />
117
+ </div>
118
+ <button type="submit" disabled={isSubmitting}>
119
+ Add Tag
120
+ </button>
121
+ </>
122
+ }
123
+ </Form>
124
+ );
125
+ }}
126
+ </Formik>
127
+ </center>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ );
132
+ }
@@ -0,0 +1,116 @@
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 className="form-error" name="username" component="span" />
67
+ </div>
68
+ <div>
69
+ <Field type="password" name="password" placeholder="Password" />
70
+ </div>
71
+ <div>
72
+ <ErrorMessage className="form-error" name="password" component="span" />
73
+ </div>
74
+ <div>
75
+ <p>
76
+ Don't have an account yet?{' '}
77
+ <a onClick={() => setShowSignup(true)}>Sign Up</a>
78
+ </p>
79
+ </div>
80
+ <button type="submit" disabled={isSubmitting}>
81
+ {isSubmitting ? <LoadingIcon /> : 'Login'}
82
+ </button>
83
+ <div>
84
+ <p>Or</p>
85
+ </div>
86
+ {process.env.REACT_ENABLE_GITHUB_OAUTH === 'true' ? (
87
+ <div>
88
+ <a href={githubLoginURL.href}>
89
+ <img
90
+ src={GitHubLoginButton}
91
+ alt="Sign in with GitHub"
92
+ width="200px"
93
+ />
94
+ </a>
95
+ </div>
96
+ ) : (
97
+ ''
98
+ )}
99
+ {process.env.REACT_ENABLE_GOOGLE_OAUTH === 'true' ? (
100
+ <div>
101
+ <a href={googleLoginURL.href}>
102
+ <img
103
+ src={GoogleLoginButton}
104
+ alt="Sign in with Google"
105
+ width="200px"
106
+ />
107
+ </a>
108
+ </div>
109
+ ) : (
110
+ ''
111
+ )}
112
+ </Form>
113
+ )}
114
+ </Formik>
115
+ );
116
+ }
@@ -0,0 +1,144 @@
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 className="form-error" name="email" component="span" />
85
+ </div>
86
+ <div>
87
+ <Field type="text" name="signupUsername" placeholder="Username" />
88
+ </div>
89
+ <div>
90
+ <ErrorMessage className="form-error" name="signupUsername" component="span" />
91
+ </div>
92
+ <div>
93
+ <Field
94
+ type="password"
95
+ name="signupPassword"
96
+ placeholder="Password"
97
+ />
98
+ </div>
99
+ <div>
100
+ <ErrorMessage className="form-error" name="signupPassword" component="span" />
101
+ </div>
102
+ <div>
103
+ <p>
104
+ Have an account already?{' '}
105
+ <a onClick={() => setShowSignup(false)}>Login</a>
106
+ </p>
107
+ </div>
108
+ <button type="submit" disabled={isSubmitting}>
109
+ {isSubmitting ? <LoadingIcon /> : 'Sign Up'}
110
+ </button>
111
+ <div>
112
+ <p>Or</p>
113
+ </div>
114
+ {process.env.REACT_ENABLE_GITHUB_OAUTH === 'true' ? (
115
+ <div>
116
+ <a href={githubLoginURL.href}>
117
+ <img
118
+ src={GitHubLoginButton}
119
+ alt="Sign in with GitHub"
120
+ width="200px"
121
+ />
122
+ </a>
123
+ </div>
124
+ ) : (
125
+ ''
126
+ )}
127
+ {process.env.REACT_ENABLE_GOOGLE_OAUTH === 'true' ? (
128
+ <div>
129
+ <a href={googleLoginURL.href}>
130
+ <img
131
+ src={GoogleLoginButton}
132
+ alt="Sign in with Google"
133
+ width="200px"
134
+ />
135
+ </a>
136
+ </div>
137
+ ) : (
138
+ ''
139
+ )}
140
+ </Form>
141
+ )}
142
+ </Formik>
143
+ );
144
+ }
@@ -33,11 +33,12 @@ describe('LoginPage', () => {
33
33
 
34
34
  await waitFor(() => {
35
35
  expect(getByText('DataJunction')).toBeInTheDocument();
36
- expect(queryAllByText('Required').length).toEqual(2);
36
+ expect(getByText('Username is required')).toBeInTheDocument();
37
+ expect(getByText('Password is required')).toBeInTheDocument();
37
38
  });
38
39
  });
39
40
 
40
- it('calls fetch with correct data on submit', async () => {
41
+ it('calls fetch with correct data on login', async () => {
41
42
  const username = 'testUser';
42
43
  const password = 'testPassword';
43
44
 
@@ -62,4 +63,35 @@ describe('LoginPage', () => {
62
63
  expect(window.location.reload).toHaveBeenCalled();
63
64
  });
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
+ });
65
97
  });
@@ -1,90 +1,17 @@
1
1
  import { useState } from 'react';
2
- import { Formik, Form, Field, ErrorMessage } from 'formik';
3
2
  import '../../../styles/login.css';
4
- import logo from '../Root/assets/dj-logo.png';
5
- import GitHubLoginButton from './assets/sign-in-with-github.png';
3
+ import SignupForm from './SignupForm';
4
+ import LoginForm from './LoginForm';
6
5
 
7
6
  export function LoginPage() {
8
- const [, setError] = useState('');
9
- const githubLoginURL = new URL('/github/login/', process.env.REACT_APP_DJ_URL)
10
- .href;
11
-
12
- const handleBasicLogin = async ({ username, password }) => {
13
- const data = new FormData();
14
- data.append('username', username);
15
- data.append('password', password);
16
- await fetch(`${process.env.REACT_APP_DJ_URL}/basic/login/`, {
17
- method: 'POST',
18
- body: data,
19
- credentials: 'include',
20
- }).catch(error => {
21
- setError(error ? JSON.stringify(error) : '');
22
- });
23
- window.location.reload();
24
- };
25
-
7
+ const [showSignup, setShowSignup] = useState(false);
26
8
  return (
27
- <div className="container">
28
- <div className="login">
29
- <center>
30
- <Formik
31
- initialValues={{ username: '', password: '' }}
32
- validate={values => {
33
- const errors = {};
34
- if (!values.username) {
35
- errors.username = 'Required';
36
- }
37
- if (!values.password) {
38
- errors.password = 'Required';
39
- }
40
- return errors;
41
- }}
42
- onSubmit={(values, { setSubmitting }) => {
43
- setTimeout(() => {
44
- handleBasicLogin(values);
45
- setSubmitting(false);
46
- }, 400);
47
- }}
48
- >
49
- {({ isSubmitting }) => (
50
- <Form>
51
- <div className="logo-title">
52
- <img src={logo} alt="DJ Logo" width="75px" height="75px" />
53
- <h2>DataJunction</h2>
54
- </div>
55
- <div className="inputContainer">
56
- <ErrorMessage name="username" component="span" />
57
- <Field type="text" name="username" placeholder="Username" />
58
- </div>
59
- <div>
60
- <ErrorMessage name="password" component="span" />
61
- <Field
62
- type="password"
63
- name="password"
64
- placeholder="Password"
65
- />
66
- </div>
67
- <button type="submit" disabled={isSubmitting}>
68
- Login
69
- </button>
70
- {process.env.REACT_ENABLE_GITHUB_OAUTH === 'true' ? (
71
- <div>
72
- <a href={githubLoginURL}>
73
- <img
74
- src={GitHubLoginButton}
75
- alt="Sign in with GitHub"
76
- width="200px"
77
- />
78
- </a>
79
- </div>
80
- ) : (
81
- ''
82
- )}
83
- </Form>
84
- )}
85
- </Formik>
86
- </center>
87
- </div>
9
+ <div className="container login">
10
+ {showSignup ? (
11
+ <SignupForm setShowSignup={setShowSignup} />
12
+ ) : (
13
+ <LoginForm setShowSignup={setShowSignup} />
14
+ )}
88
15
  </div>
89
16
  );
90
17
  }
@@ -145,6 +145,11 @@ export function NamespacePage() {
145
145
  Dimension
146
146
  </div>
147
147
  </a>
148
+ <a href={`/create/tag`}>
149
+ <div className="entity__tag node_type_creation_heading">
150
+ Tag
151
+ </div>
152
+ </a>
148
153
  </div>
149
154
  </div>
150
155
  </span>
@@ -1,10 +1,23 @@
1
1
  import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
2
- import { useState } from 'react';
2
+ import { useEffect, useRef, useState } from 'react';
3
3
  import { nightOwl } from 'react-syntax-highlighter/src/styles/hljs';
4
4
  import PythonIcon from '../../icons/PythonIcon';
5
5
 
6
6
  export default function ClientCodePopover({ code }) {
7
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]);
8
21
 
9
22
  return (
10
23
  <>
@@ -22,6 +35,7 @@ export default function ClientCodePopover({ code }) {
22
35
  role="dialog"
23
36
  aria-label="client-code"
24
37
  style={{ display: codeAnchor === false ? 'none' : 'block' }}
38
+ ref={ref}
25
39
  >
26
40
  <SyntaxHighlighter language="python" style={nightOwl}>
27
41
  {code}
@@ -1,4 +1,4 @@
1
- import { useContext, useState } from 'react';
1
+ import { useContext, useEffect, useRef, useState } from 'react';
2
2
  import * as React from 'react';
3
3
  import DJClientContext from '../../providers/djclient';
4
4
  import { Form, Formik } from 'formik';
@@ -9,6 +9,19 @@ import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
9
9
  export default function EditColumnPopover({ column, node, options, onSubmit }) {
10
10
  const djClient = useContext(DJClientContext).DataJunctionAPI;
11
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]);
12
25
 
13
26
  const saveAttributes = async (
14
27
  { node, column, attributes },
@@ -44,6 +57,7 @@ export default function EditColumnPopover({ column, node, options, onSubmit }) {
44
57
  role="dialog"
45
58
  aria-label="client-code"
46
59
  style={{ display: popoverAnchor === false ? 'none' : 'block' }}
60
+ ref={ref}
47
61
  >
48
62
  <Formik
49
63
  initialValues={{
@@ -1,4 +1,4 @@
1
- import { useContext, useState } from 'react';
1
+ import { useContext, useEffect, useRef, useState } from 'react';
2
2
  import * as React from 'react';
3
3
  import DJClientContext from '../../providers/djclient';
4
4
  import { Form, Formik } from 'formik';
@@ -14,6 +14,20 @@ export default function LinkDimensionPopover({
14
14
  }) {
15
15
  const djClient = useContext(DJClientContext).DataJunctionAPI;
16
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
+
17
31
  const columnDimension = column.dimension;
18
32
 
19
33
  const handleSubmit = async (
@@ -73,6 +87,7 @@ export default function LinkDimensionPopover({
73
87
  role="dialog"
74
88
  aria-label="client-code"
75
89
  style={{ display: popoverAnchor === false ? 'none' : 'block' }}
90
+ ref={ref}
76
91
  >
77
92
  <Formik
78
93
  initialValues={{
@@ -60,6 +60,16 @@ export default function NodeColumnTab({ node, djClient }) {
60
60
  >
61
61
  {col.name}
62
62
  </td>
63
+ <td>
64
+ <span
65
+ className=""
66
+ role="columnheader"
67
+ aria-label="ColumnDisplayName"
68
+ aria-hidden="false"
69
+ >
70
+ {col.display_name}
71
+ </span>
72
+ </td>
63
73
  <td>
64
74
  <span
65
75
  className="node_type__transform badge node_type"
@@ -111,6 +121,7 @@ export default function NodeColumnTab({ node, djClient }) {
111
121
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
112
122
  <tr>
113
123
  <th className="text-start">Column</th>
124
+ <th>Display Name</th>
114
125
  <th>Type</th>
115
126
  <th>Linked Dimension</th>
116
127
  <th>Attributes</th>