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,55 @@
1
+ import DJClientContext from '../providers/djclient';
2
+ import * as React from 'react';
3
+ import DeleteIcon from '../icons/DeleteIcon';
4
+ import { Form, Formik } from 'formik';
5
+ import { useContext } from 'react';
6
+ import { displayMessageAfterSubmit } from '../../utils/form';
7
+
8
+ export default function DeleteNode({ nodeName }) {
9
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
10
+ const deleteNode = async (values, { setSubmitting, setStatus }) => {
11
+ const { status, json } = await djClient.deactivate(values.nodeName);
12
+ if (status === 200 || status === 201 || status === 204) {
13
+ setStatus({
14
+ success: <>Successfully deleted node {values.nodeName}</>,
15
+ });
16
+ } else {
17
+ setStatus({
18
+ failure: `${json.message}`,
19
+ });
20
+ }
21
+ setSubmitting(false);
22
+ };
23
+
24
+ const initialValues = {
25
+ nodeName: nodeName,
26
+ };
27
+
28
+ return (
29
+ <Formik initialValues={initialValues} onSubmit={deleteNode}>
30
+ {function Render({ isSubmitting, status, setFieldValue }) {
31
+ return (
32
+ <Form className="deleteNode">
33
+ {displayMessageAfterSubmit(status)}
34
+ {
35
+ <>
36
+ <button
37
+ type="submit"
38
+ disabled={isSubmitting}
39
+ style={{
40
+ marginLeft: 0,
41
+ all: 'unset',
42
+ color: '#005c72',
43
+ cursor: 'pointer',
44
+ }}
45
+ >
46
+ <DeleteIcon />
47
+ </button>
48
+ </>
49
+ }
50
+ </Form>
51
+ );
52
+ }}
53
+ </Formik>
54
+ );
55
+ }
@@ -0,0 +1,24 @@
1
+ import { Component } from 'react';
2
+
3
+ export default class ListGroupItem extends Component {
4
+ render() {
5
+ const { label, value } = this.props;
6
+ return (
7
+ <div className="list-group-item d-flex">
8
+ <div className="d-flex gap-2 w-100 justify-content-between py-3">
9
+ <div>
10
+ <h6 className="mb-0 w-100">{label}</h6>
11
+ <p
12
+ className="mb-0 opacity-75"
13
+ role="dialog"
14
+ aria-hidden="false"
15
+ aria-label={label}
16
+ >
17
+ {value}
18
+ </p>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ );
23
+ }
24
+ }
@@ -0,0 +1,31 @@
1
+ import { Component } from 'react';
2
+ import HorizontalHierarchyIcon from '../icons/HorizontalHierarchyIcon';
3
+
4
+ export default class NamespaceHeader extends Component {
5
+ render() {
6
+ const { namespace } = this.props;
7
+ const namespaceParts = namespace.split('.');
8
+ const namespaceList = namespaceParts.map((piece, index) => {
9
+ return (
10
+ <li className="breadcrumb-item" key={index}>
11
+ <a
12
+ className="link-body-emphasis"
13
+ href={'/namespaces/' + namespaceParts.slice(0, index + 1).join('.')}
14
+ >
15
+ {piece}
16
+ </a>
17
+ </li>
18
+ );
19
+ });
20
+ return (
21
+ <ol className="breadcrumb breadcrumb-chevron p-3 bg-body-tertiary rounded-3">
22
+ <li className="breadcrumb-item">
23
+ <a href="/">
24
+ <HorizontalHierarchyIcon />
25
+ </a>
26
+ </li>
27
+ {namespaceList}
28
+ </ol>
29
+ );
30
+ }
31
+ }
@@ -0,0 +1,77 @@
1
+ export default function QueryInfo({
2
+ id,
3
+ state,
4
+ engine_name,
5
+ engine_version,
6
+ errors,
7
+ links,
8
+ output_table,
9
+ scheduled,
10
+ started,
11
+ numRows,
12
+ }) {
13
+ return (
14
+ <div className="table-responsive">
15
+ <table className="card-inner-table table">
16
+ <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
17
+ <tr>
18
+ <th>Query ID</th>
19
+ <th>Engine</th>
20
+ <th>State</th>
21
+ <th>Scheduled</th>
22
+ <th>Started</th>
23
+ <th>Errors</th>
24
+ <th>Links</th>
25
+ <th>Output Table</th>
26
+ <th>Number of Rows</th>
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ <tr>
31
+ <td>
32
+ <span className="rounded-pill badge bg-secondary-soft">{id}</span>
33
+ </td>
34
+ <td>
35
+ <span className="rounded-pill badge bg-secondary-soft">
36
+ {engine_name}
37
+ {' - '}
38
+ {engine_version}
39
+ </span>
40
+ </td>
41
+ <td>{state}</td>
42
+ <td>{scheduled}</td>
43
+ <td>{started}</td>
44
+ <td>
45
+ {errors?.length ? (
46
+ errors.map((e, idx) => (
47
+ <p key={`error-${idx}`}>
48
+ <span className="rounded-pill badge bg-secondary-error">
49
+ {e}
50
+ </span>
51
+ </p>
52
+ ))
53
+ ) : (
54
+ <></>
55
+ )}
56
+ </td>
57
+ <td>
58
+ {links?.length ? (
59
+ links.map((link, idx) => (
60
+ <p key={idx}>
61
+ <a href={link} target="_blank" rel="noreferrer">
62
+ {link}
63
+ </a>
64
+ </p>
65
+ ))
66
+ ) : (
67
+ <></>
68
+ )}
69
+ </td>
70
+ <td>{output_table}</td>
71
+ <td>{numRows}</td>
72
+ </tr>
73
+ </tbody>
74
+ </table>
75
+ </div>
76
+ );
77
+ }
@@ -0,0 +1,25 @@
1
+ import { Component } from 'react';
2
+
3
+ export default class Tab extends Component {
4
+ render() {
5
+ const { id, onClick, selectedTab } = this.props;
6
+ return (
7
+ <div className={selectedTab === id ? 'col active' : 'col'}>
8
+ <div className="header-tabs nav-overflow nav nav-tabs">
9
+ <div className="nav-item">
10
+ <button
11
+ id={id}
12
+ className="nav-link"
13
+ tabIndex="0"
14
+ onClick={onClick}
15
+ aria-label={this.props.name}
16
+ aria-hidden="false"
17
+ >
18
+ {this.props.name}
19
+ </button>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ );
24
+ }
25
+ }
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+
3
+ const ToggleSwitch = ({ checked, onChange, toggleName }) => (
4
+ <>
5
+ <input
6
+ id="show-compiled-sql-toggle"
7
+ role="checkbox"
8
+ aria-label="ToggleSwitch"
9
+ aria-hidden="false"
10
+ type="checkbox"
11
+ className="checkbox"
12
+ checked={checked}
13
+ onChange={e => onChange(e.target.checked)}
14
+ />
15
+ <label htmlFor="show-compiled-sql-toggle" className="switch"></label>{' '}
16
+ {toggleName}
17
+ </>
18
+ );
19
+
20
+ export default ToggleSwitch;
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import fetchMock from 'jest-fetch-mock';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { render } from '../../../setupTests';
6
+ import DJClientContext from '../../providers/djclient';
7
+ import DeleteNode from '../DeleteNode';
8
+
9
+ describe('<DeleteNode />', () => {
10
+ beforeEach(() => {
11
+ fetchMock.resetMocks();
12
+ jest.clearAllMocks();
13
+ window.scrollTo = jest.fn();
14
+ });
15
+
16
+ const renderElement = djClient => {
17
+ return render(
18
+ <DJClientContext.Provider value={djClient}>
19
+ <DeleteNode nodeName="default.hard_hat" />
20
+ </DJClientContext.Provider>,
21
+ );
22
+ };
23
+
24
+ const initializeMockDJClient = () => {
25
+ return {
26
+ DataJunctionAPI: {
27
+ deactivate: jest.fn(),
28
+ },
29
+ };
30
+ };
31
+
32
+ it('deletes a node when clicked', async () => {
33
+ const mockDjClient = initializeMockDJClient();
34
+ mockDjClient.DataJunctionAPI.deactivate.mockReturnValue({
35
+ status: 204,
36
+ json: { name: 'source.warehouse.schema.some_table' },
37
+ });
38
+
39
+ renderElement(mockDjClient);
40
+
41
+ await userEvent.click(screen.getByRole('button'));
42
+
43
+ await waitFor(() => {
44
+ expect(mockDjClient.DataJunctionAPI.deactivate).toBeCalled();
45
+ expect(mockDjClient.DataJunctionAPI.deactivate).toBeCalledWith(
46
+ 'default.hard_hat',
47
+ );
48
+ });
49
+ expect(
50
+ screen.getByText('Successfully deleted node default.hard_hat'),
51
+ ).toBeInTheDocument();
52
+ }, 60000);
53
+ });
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import { createRenderer } from 'react-test-renderer/shallow';
3
+
4
+ import ListGroupItem from '../ListGroupItem';
5
+
6
+ const renderer = createRenderer();
7
+
8
+ describe('<ListGroupItem />', () => {
9
+ it('should render and match the snapshot', () => {
10
+ renderer.render(
11
+ <ListGroupItem label="Name" value={<span>Something</span>} />,
12
+ );
13
+ const renderedOutput = renderer.getRenderOutput();
14
+ expect(renderedOutput).toMatchSnapshot();
15
+ });
16
+ });
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import { createRenderer } from 'react-test-renderer/shallow';
3
+
4
+ import NamespaceHeader from '../NamespaceHeader';
5
+
6
+ const renderer = createRenderer();
7
+
8
+ describe('<NamespaceHeader />', () => {
9
+ it('should render and match the snapshot', () => {
10
+ renderer.render(<NamespaceHeader namespace="shared.dimensions.accounts" />);
11
+ const renderedOutput = renderer.getRenderOutput();
12
+ expect(renderedOutput).toMatchSnapshot();
13
+ });
14
+ });
@@ -0,0 +1,55 @@
1
+ import * as React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import QueryInfo from '../QueryInfo';
4
+
5
+ describe('<QueryInfo />', () => {
6
+ const defaultProps = {
7
+ id: '123',
8
+ state: 'Running',
9
+ engine_name: 'Spark SQL',
10
+ engine_version: '1.0',
11
+ errors: ['Error 1', 'Error 2'],
12
+ links: ['http://example.com', 'http://example2.com'],
13
+ output_table: 'table1',
14
+ scheduled: '2023-09-06',
15
+ started: '2023-09-07',
16
+ numRows: 1000,
17
+ };
18
+
19
+ it('renders without crashing', () => {
20
+ render(<QueryInfo {...defaultProps} />);
21
+ });
22
+
23
+ it('displays correct query information', () => {
24
+ render(<QueryInfo {...defaultProps} />);
25
+
26
+ expect(screen.getByText(defaultProps.id)).toBeInTheDocument();
27
+ expect(
28
+ screen.getByText(
29
+ `${defaultProps.engine_name} - ${defaultProps.engine_version}`,
30
+ ),
31
+ ).toBeInTheDocument();
32
+ expect(screen.getByText(defaultProps.state)).toBeInTheDocument();
33
+ expect(screen.getByText(defaultProps.scheduled)).toBeInTheDocument();
34
+ expect(screen.getByText(defaultProps.started)).toBeInTheDocument();
35
+ expect(screen.getByText(defaultProps.output_table)).toBeInTheDocument();
36
+ expect(screen.getByText(String(defaultProps.numRows))).toBeInTheDocument();
37
+ defaultProps.errors.forEach(error => {
38
+ expect(screen.getByText(error)).toBeInTheDocument();
39
+ });
40
+ defaultProps.links.forEach(link => {
41
+ expect(screen.getByText(link)).toHaveAttribute('href', link);
42
+ });
43
+ });
44
+
45
+ it('does not render errors and links when they are not provided', () => {
46
+ render(<QueryInfo {...defaultProps} errors={[]} links={[]} />);
47
+
48
+ defaultProps.errors.forEach(error => {
49
+ expect(screen.queryByText(error)).not.toBeInTheDocument();
50
+ });
51
+ defaultProps.links.forEach(link => {
52
+ expect(screen.queryByText(link)).not.toBeInTheDocument();
53
+ });
54
+ });
55
+ });
@@ -0,0 +1,27 @@
1
+ import * as React from 'react';
2
+ import { render, fireEvent } from '@testing-library/react';
3
+
4
+ import Tab from '../Tab';
5
+
6
+ describe('<Tab />', () => {
7
+ it('renders without crashing', () => {
8
+ render(<Tab />);
9
+ });
10
+
11
+ it('has the active class when selectedTab matches id', () => {
12
+ const { container } = render(<Tab id="1" selectedTab="1" />);
13
+ expect(container.querySelector('.col')).toHaveClass('active');
14
+ });
15
+
16
+ it('does not have the active class when selectedTab does not match id', () => {
17
+ const { container } = render(<Tab id="1" selectedTab="2" />);
18
+ expect(container.querySelector('.col')).not.toHaveClass('active');
19
+ });
20
+
21
+ it('calls onClick when the button is clicked', () => {
22
+ const onClickMock = jest.fn();
23
+ const { getByRole } = render(<Tab id="1" onClick={onClickMock} />);
24
+ fireEvent.click(getByRole('button'));
25
+ expect(onClickMock).toHaveBeenCalledTimes(1);
26
+ });
27
+ });
@@ -0,0 +1,43 @@
1
+ import React from 'react';
2
+ import { render, fireEvent, screen } from '@testing-library/react';
3
+ import ToggleSwitch from '../ToggleSwitch';
4
+
5
+ describe('<ToggleSwitch />', () => {
6
+ const defaultProps = {
7
+ checked: false,
8
+ onChange: jest.fn(),
9
+ toggleName: 'Toggle Switch',
10
+ };
11
+
12
+ it('renders without crashing', () => {
13
+ render(<ToggleSwitch {...defaultProps} />);
14
+ });
15
+
16
+ it('displays the correct toggle name', () => {
17
+ render(<ToggleSwitch {...defaultProps} />);
18
+ expect(screen.getByText(defaultProps.toggleName)).toBeInTheDocument();
19
+ });
20
+
21
+ it('reflects the checked state correctly', () => {
22
+ render(<ToggleSwitch {...defaultProps} checked={true} />);
23
+ const checkbox = screen.getByRole('checkbox');
24
+ expect(checkbox).toBeChecked();
25
+ });
26
+
27
+ it('calls onChange with the correct value when toggled', () => {
28
+ render(<ToggleSwitch {...defaultProps} />);
29
+ const checkbox = screen.getByRole('checkbox');
30
+
31
+ fireEvent.click(checkbox);
32
+ expect(defaultProps.onChange).toHaveBeenCalledWith(true);
33
+
34
+ fireEvent.click(checkbox);
35
+ expect(checkbox).not.toBeChecked();
36
+ });
37
+
38
+ it('is unchecked by default if no checked prop is provided', () => {
39
+ render(<ToggleSwitch onChange={jest.fn()} toggleName="Test Toggle" />);
40
+ const checkbox = screen.getByRole('checkbox');
41
+ expect(checkbox).not.toBeChecked();
42
+ });
43
+ });
@@ -0,0 +1,29 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<ListGroupItem /> should render and match the snapshot 1`] = `
4
+ <div
5
+ className="list-group-item d-flex"
6
+ >
7
+ <div
8
+ className="d-flex gap-2 w-100 justify-content-between py-3"
9
+ >
10
+ <div>
11
+ <h6
12
+ className="mb-0 w-100"
13
+ >
14
+ Name
15
+ </h6>
16
+ <p
17
+ aria-hidden="false"
18
+ aria-label="Name"
19
+ className="mb-0 opacity-75"
20
+ role="dialog"
21
+ >
22
+ <span>
23
+ Something
24
+ </span>
25
+ </p>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ `;
@@ -0,0 +1,47 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`<NamespaceHeader /> should render and match the snapshot 1`] = `
4
+ <ol
5
+ className="breadcrumb breadcrumb-chevron p-3 bg-body-tertiary rounded-3"
6
+ >
7
+ <li
8
+ className="breadcrumb-item"
9
+ >
10
+ <a
11
+ href="/"
12
+ >
13
+ <HorizontalHierarchyIcon />
14
+ </a>
15
+ </li>
16
+ <li
17
+ className="breadcrumb-item"
18
+ >
19
+ <a
20
+ className="link-body-emphasis"
21
+ href="/namespaces/shared"
22
+ >
23
+ shared
24
+ </a>
25
+ </li>
26
+ <li
27
+ className="breadcrumb-item"
28
+ >
29
+ <a
30
+ className="link-body-emphasis"
31
+ href="/namespaces/shared.dimensions"
32
+ >
33
+ dimensions
34
+ </a>
35
+ </li>
36
+ <li
37
+ className="breadcrumb-item"
38
+ >
39
+ <a
40
+ className="link-body-emphasis"
41
+ href="/namespaces/shared.dimensions.accounts"
42
+ >
43
+ accounts
44
+ </a>
45
+ </li>
46
+ </ol>
47
+ `;
@@ -0,0 +1,46 @@
1
+ import React from 'react';
2
+ import { DJNodeDimensions } from './DJNodeDimensions';
3
+ import { DJNodeColumns } from './DJNodeColumns';
4
+
5
+ export default function Collapse({ collapsed, text, data }) {
6
+ const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
7
+
8
+ return (
9
+ <>
10
+ <div className="collapse">
11
+ {data.type === 'metric' ? (
12
+ <button
13
+ className="collapse-button"
14
+ onClick={() => setIsCollapsed(!isCollapsed)}
15
+ >
16
+ {isCollapsed ? '\u25B6 Show' : '\u25BC Hide'} {text}
17
+ </button>
18
+ ) : (
19
+ ''
20
+ )}
21
+ <div
22
+ className={`collapse-content ${
23
+ isCollapsed && data.type === 'metric' ? 'collapsed' : 'expanded'
24
+ }`}
25
+ aria-expanded={isCollapsed}
26
+ >
27
+ {data.type !== 'metric'
28
+ ? isCollapsed
29
+ ? DJNodeColumns({ data: data, limit: 10 })
30
+ : DJNodeColumns({ data: data, limit: 100 })
31
+ : DJNodeDimensions(data)}
32
+ </div>
33
+ {data.type !== 'metric' && data.column_names.length > 10 ? (
34
+ <button
35
+ className="collapse-button"
36
+ onClick={() => setIsCollapsed(!isCollapsed)}
37
+ >
38
+ {isCollapsed ? '\u25B6 More' : '\u25BC Less'} {text}
39
+ </button>
40
+ ) : (
41
+ ''
42
+ )}
43
+ </div>
44
+ </>
45
+ );
46
+ }
@@ -0,0 +1,89 @@
1
+ import { memo } from 'react';
2
+ import { Handle, Position } from 'reactflow';
3
+ import Collapse from './Collapse';
4
+
5
+ function capitalize(string) {
6
+ return string.charAt(0).toUpperCase() + string.slice(1);
7
+ }
8
+
9
+ export function DJNode({ id, data }) {
10
+ const handleWrapperStyle = {
11
+ display: 'flex',
12
+ position: 'absolute',
13
+ height: '100%',
14
+ flexDirection: 'column',
15
+ top: '50%',
16
+ justifyContent: 'space-between',
17
+ };
18
+ const handleWrapperStyleRight = { ...handleWrapperStyle, ...{ right: 0 } };
19
+
20
+ const handleStyle = {
21
+ width: '12px',
22
+ height: '12px',
23
+ borderRadius: '12px',
24
+ background: 'transparent',
25
+ border: '4px solid transparent',
26
+ cursor: 'pointer',
27
+ position: 'absolute',
28
+ top: '0px',
29
+ left: 0,
30
+ };
31
+ const handleStyleLeft = percentage => {
32
+ return {
33
+ ...handleStyle,
34
+ ...{
35
+ transform: 'translate(-' + percentage + '%, -50%)',
36
+ },
37
+ };
38
+ };
39
+ const highlightNodeClass =
40
+ data.is_current === true ? ' dj-node_highlight' : '';
41
+ return (
42
+ <>
43
+ <div
44
+ className={'dj-node__full node_type__' + data.type + highlightNodeClass}
45
+ key={data.name}
46
+ style={{ width: '450px' }}
47
+ >
48
+ <div style={handleWrapperStyle}>
49
+ <Handle
50
+ type="target"
51
+ id={data.name}
52
+ position={Position.Left}
53
+ style={handleStyleLeft(100)}
54
+ />
55
+ </div>
56
+ <div className="dj-node__header">
57
+ <div className="serif">
58
+ {data.name
59
+ .split('.')
60
+ .slice(0, data.name.split('.').length - 1)
61
+ .join(' \u25B6 ')}
62
+ </div>
63
+ </div>
64
+ <div className="dj-node__body">
65
+ <b>{capitalize(data.type)}</b>
66
+ <br />{' '}
67
+ <a href={`/nodes/${data.name}`}>
68
+ {data.type === 'source' ? data.table : data.display_name}
69
+ </a>
70
+ <Collapse
71
+ collapsed={true}
72
+ text={data.type !== 'metric' ? 'columns' : 'dimensions'}
73
+ data={data}
74
+ />
75
+ </div>
76
+ <div style={handleWrapperStyleRight}>
77
+ <Handle
78
+ type="source"
79
+ id={data.name}
80
+ position={Position.Right}
81
+ style={handleStyleLeft(90)}
82
+ />
83
+ </div>
84
+ </div>
85
+ </>
86
+ );
87
+ }
88
+
89
+ export default memo(DJNode);