datajunction-ui 0.0.1-a1 → 0.0.1-a101

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 (110) hide show
  1. package/Makefile +7 -1
  2. package/package.json +18 -7
  3. package/public/index.html +1 -1
  4. package/src/app/components/AddNodeDropdown.jsx +44 -0
  5. package/src/app/components/ListGroupItem.jsx +2 -1
  6. package/src/app/components/NodeListActions.jsx +69 -0
  7. package/src/app/components/NodeMaterializationDelete.jsx +80 -0
  8. package/src/app/components/QueryInfo.jsx +96 -1
  9. package/src/app/components/Search.jsx +94 -0
  10. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  11. package/src/app/components/__tests__/Search.test.jsx +63 -0
  12. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +5 -3
  13. package/src/app/components/djgraph/Collapse.jsx +3 -2
  14. package/src/app/components/djgraph/DJNode.jsx +1 -1
  15. package/src/app/components/djgraph/DJNodeColumns.jsx +5 -1
  16. package/src/app/components/djgraph/LayoutFlow.jsx +5 -3
  17. package/src/app/components/forms/Action.jsx +8 -0
  18. package/src/app/components/forms/NodeNameField.jsx +64 -0
  19. package/src/app/components/search.css +17 -0
  20. package/src/app/icons/AddItemIcon.jsx +16 -0
  21. package/src/app/icons/CommitIcon.jsx +45 -0
  22. package/src/app/icons/DiffIcon.jsx +63 -0
  23. package/src/app/icons/EyeIcon.jsx +20 -0
  24. package/src/app/icons/FilterIcon.jsx +7 -0
  25. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  26. package/src/app/icons/LoadingIcon.jsx +10 -10
  27. package/src/app/icons/PythonIcon.jsx +6 -44
  28. package/src/app/index.tsx +24 -0
  29. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  30. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  31. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  32. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  33. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +5 -0
  34. package/src/app/pages/AddEditNodePage/FullNameField.jsx +3 -2
  35. package/src/app/pages/AddEditNodePage/Loadable.jsx +6 -2
  36. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  37. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  38. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  39. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  40. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +8 -3
  41. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  42. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  43. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  44. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +15 -9
  45. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +167 -24
  46. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +55 -25
  47. package/src/app/pages/AddEditNodePage/index.jsx +275 -194
  48. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +154 -0
  49. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  50. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +77 -0
  51. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +405 -0
  52. package/src/app/pages/CubeBuilderPage/index.jsx +267 -0
  53. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +5 -5
  54. package/src/app/pages/NamespacePage/Explorer.jsx +6 -2
  55. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  56. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  57. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  58. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  59. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +98 -19
  60. package/src/app/pages/NamespacePage/index.jsx +272 -89
  61. package/src/app/pages/NodePage/AddBackfillPopover.jsx +60 -61
  62. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +104 -51
  63. package/src/app/pages/NodePage/ClientCodePopover.jsx +73 -25
  64. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  65. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  66. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +38 -23
  67. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  68. package/src/app/pages/NodePage/NodeColumnTab.jsx +183 -113
  69. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  70. package/src/app/pages/NodePage/NodeGraphTab.jsx +56 -29
  71. package/src/app/pages/NodePage/NodeHistory.jsx +165 -161
  72. package/src/app/pages/NodePage/NodeInfoTab.jsx +148 -14
  73. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +201 -104
  74. package/src/app/pages/NodePage/NodeStatus.jsx +96 -21
  75. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  76. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  77. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +3 -5
  78. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  79. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  80. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  81. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +13 -4
  82. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  83. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  84. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  85. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +10 -14
  86. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  87. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  88. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +6 -2
  89. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +3 -2
  90. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +148 -0
  91. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +159 -57
  92. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  93. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +2 -386
  94. package/src/app/pages/NodePage/index.jsx +94 -57
  95. package/src/app/pages/Root/__tests__/index.test.jsx +3 -1
  96. package/src/app/pages/Root/index.tsx +62 -12
  97. package/src/app/services/DJService.js +587 -55
  98. package/src/app/services/__tests__/DJService.test.jsx +382 -45
  99. package/src/index.tsx +1 -0
  100. package/src/mocks/mockNodes.jsx +265 -227
  101. package/src/styles/dag.css +4 -2
  102. package/src/styles/index.css +474 -10
  103. package/src/styles/loading.css +1 -1
  104. package/src/styles/node-creation.scss +84 -5
  105. package/src/styles/node-list.css +4 -0
  106. package/src/styles/sorted-table.css +15 -0
  107. package/src/app/components/DeleteNode.jsx +0 -55
  108. package/src/app/components/__tests__/DeleteNode.test.jsx +0 -53
  109. package/src/app/pages/NodePage/NodeSQLTab.jsx +0 -82
  110. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +0 -49
@@ -19,9 +19,11 @@ exports[`<ListGroupItem /> should render and match the snapshot 1`] = `
19
19
  className="mb-0 opacity-75"
20
20
  role="dialog"
21
21
  >
22
- <span>
23
- Something
24
- </span>
22
+ <Markdown>
23
+ <span>
24
+ Something
25
+ </span>
26
+ </Markdown>
25
27
  </p>
26
28
  </div>
27
29
  </div>
@@ -5,6 +5,7 @@ import { DJNodeColumns } from './DJNodeColumns';
5
5
  export default function Collapse({ collapsed, text, data }) {
6
6
  const [isCollapsed, setIsCollapsed] = React.useState(collapsed);
7
7
 
8
+ const limit = 5;
8
9
  return (
9
10
  <>
10
11
  <div className="collapse">
@@ -26,11 +27,11 @@ export default function Collapse({ collapsed, text, data }) {
26
27
  >
27
28
  {data.type !== 'metric'
28
29
  ? isCollapsed
29
- ? DJNodeColumns({ data: data, limit: 10 })
30
+ ? DJNodeColumns({ data: data, limit: limit })
30
31
  : DJNodeColumns({ data: data, limit: 100 })
31
32
  : DJNodeDimensions(data)}
32
33
  </div>
33
- {data.type !== 'metric' && data.column_names.length > 10 ? (
34
+ {data.type !== 'metric' && data.column_names.length > limit ? (
34
35
  <button
35
36
  className="collapse-button"
36
37
  onClick={() => setIsCollapsed(!isCollapsed)}
@@ -68,7 +68,7 @@ export function DJNode({ id, data }) {
68
68
  {data.type === 'source' ? data.table : data.display_name}
69
69
  </a>
70
70
  <Collapse
71
- collapsed={true}
71
+ collapsed={data.is_current && data.type != 'metric' ? false : true}
72
72
  text={data.type !== 'metric' ? 'columns' : 'dimensions'}
73
73
  data={data}
74
74
  />
@@ -33,7 +33,11 @@ export function DJNodeColumns({ data, limit }) {
33
33
  };
34
34
  return data.column_names.slice(0, limit).map(col => (
35
35
  <div
36
- className={'custom-node-subheader node_type__' + data.type}
36
+ className={
37
+ 'custom-node-subheader node_type__' +
38
+ data.type +
39
+ (col.order <= 0 ? ' custom-node-emphasis' : '')
40
+ }
37
41
  key={`${data.name}.${col.name}`}
38
42
  >
39
43
  <div style={handleWrapperStyle}>
@@ -32,8 +32,10 @@ const getLayoutedElements = (
32
32
  const nodeHeightTracker = {};
33
33
 
34
34
  nodes.forEach(node => {
35
- nodeHeightTracker[node.id] =
36
- Math.min(node.data.column_names.length, 10) * 40 + 250;
35
+ const minColumnsLength = node.data.column_names.filter(
36
+ col => col.order > 0,
37
+ ).length;
38
+ nodeHeightTracker[node.id] = Math.min(minColumnsLength, 5) * 40 + 250;
37
39
  dagreGraph.setNode(node.id, {
38
40
  width: nodeWidth,
39
41
  height: nodeHeightTracker[node.id],
@@ -52,7 +54,7 @@ const getLayoutedElements = (
52
54
  node.sourcePosition = isHorizontal ? 'right' : 'bottom';
53
55
  node.position = {
54
56
  x: nodeWithPosition.x - nodeWidth / 2,
55
- y: nodeWithPosition.y - nodeHeightTracker[node.id] / 2,
57
+ y: nodeWithPosition.y - nodeHeightTracker[node.id] / 3,
56
58
  };
57
59
  node.width = nodeWidth;
58
60
  node.height = nodeHeightTracker[node.id];
@@ -0,0 +1,8 @@
1
+ export class Action {
2
+ static Add = new Action('add');
3
+ static Edit = new Action('edit');
4
+
5
+ constructor(name) {
6
+ this.name = name;
7
+ }
8
+ }
@@ -0,0 +1,64 @@
1
+ import { ErrorMessage, Field } from 'formik';
2
+ import { FormikSelect } from '../../pages/AddEditNodePage/FormikSelect';
3
+ import { FullNameField } from '../../pages/AddEditNodePage/FullNameField';
4
+ import React, { useContext, useEffect, useState } from 'react';
5
+ import DJClientContext from '../../providers/djclient';
6
+ import { useParams } from 'react-router-dom';
7
+
8
+ /*
9
+ * This component creates the namespace selector, display name input, and
10
+ * derived name fields in a form. It can be reused any time we need to create
11
+ * a new node.
12
+ */
13
+
14
+ export default function NodeNameField() {
15
+ const [namespaces, setNamespaces] = useState([]);
16
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
17
+ let { initialNamespace } = useParams();
18
+
19
+ useEffect(() => {
20
+ const fetchData = async () => {
21
+ const namespaces = await djClient.namespaces();
22
+ setNamespaces(
23
+ namespaces.map(m => ({
24
+ value: m['namespace'],
25
+ label: m['namespace'],
26
+ })),
27
+ );
28
+ };
29
+ fetchData().catch(console.error);
30
+ }, [djClient, djClient.metrics]);
31
+
32
+ return (
33
+ <>
34
+ <div className="NamespaceInput">
35
+ <ErrorMessage name="namespace" component="span" />
36
+ <label htmlFor="namespace">Namespace *</label>
37
+ <FormikSelect
38
+ selectOptions={namespaces}
39
+ formikFieldName="namespace"
40
+ placeholder="Choose Namespace"
41
+ defaultValue={{
42
+ value: initialNamespace,
43
+ label: initialNamespace,
44
+ }}
45
+ />
46
+ </div>
47
+ <div className="DisplayNameInput NodeCreationInput">
48
+ <ErrorMessage name="display_name" component="span" />
49
+ <label htmlFor="displayName">Display Name *</label>
50
+ <Field
51
+ type="text"
52
+ name="display_name"
53
+ id="displayName"
54
+ placeholder="Human readable display name"
55
+ />
56
+ </div>
57
+ <div className="FullNameInput NodeCreationInput">
58
+ <ErrorMessage name="name" component="span" />
59
+ <label htmlFor="FullName">Full Name</label>
60
+ <FullNameField type="text" name="name" />
61
+ </div>
62
+ </>
63
+ );
64
+ }
@@ -0,0 +1,17 @@
1
+ .search-box {
2
+ display: flex;
3
+ height: 50%;
4
+ }
5
+
6
+ .search-results {
7
+ position: absolute;
8
+ z-index: 1000;
9
+ width: 75%;
10
+ background-color: rgba(244, 244, 244, 0.8);
11
+ border-radius: 1rem;
12
+ }
13
+
14
+ .search-result-item {
15
+ text-decoration: wavy;
16
+ padding: 0.5rem;
17
+ }
@@ -0,0 +1,16 @@
1
+ const AddItemIcon = props => (
2
+ <svg
3
+ enableBackground="new 0 0 512 512"
4
+ height="20px"
5
+ id="Layer_1"
6
+ version="1.1"
7
+ viewBox="0 0 512 512"
8
+ width="20px"
9
+ xmlSpace="preserve"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ xmlnsXlink="http://www.w3.org/1999/xlink"
12
+ >
13
+ <path d="M256,512C114.625,512,0,397.391,0,256C0,114.609,114.625,0,256,0c141.391,0,256,114.609,256,256 C512,397.391,397.391,512,256,512z M256,64C149.969,64,64,149.969,64,256s85.969,192,192,192c106.047,0,192-85.969,192-192 S362.047,64,256,64z M288,384h-64v-96h-96v-64h96v-96h64v96h96v64h-96V384z" />
14
+ </svg>
15
+ );
16
+ export default AddItemIcon;
@@ -0,0 +1,45 @@
1
+ import * as React from 'react';
2
+
3
+ const CommitIcon = props => (
4
+ <svg
5
+ width="2em"
6
+ height="2em"
7
+ viewBox="0 0 256 256"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ >
10
+ <rect fill="none" height="256" width="256" />
11
+ <circle
12
+ cx="128"
13
+ cy="128"
14
+ fill="none"
15
+ r="52"
16
+ stroke="#000"
17
+ strokeLinecap="round"
18
+ strokeLinejoin="round"
19
+ strokeWidth="12"
20
+ />
21
+ <line
22
+ fill="none"
23
+ stroke="#000"
24
+ strokeLinecap="round"
25
+ strokeLinejoin="round"
26
+ strokeWidth="12"
27
+ x1="8"
28
+ x2="76"
29
+ y1="128"
30
+ y2="128"
31
+ />
32
+ <line
33
+ fill="none"
34
+ stroke="#000"
35
+ strokeLinecap="round"
36
+ strokeLinejoin="round"
37
+ strokeWidth="12"
38
+ x1="180"
39
+ x2="248"
40
+ y1="128"
41
+ y2="128"
42
+ />
43
+ </svg>
44
+ );
45
+ export default CommitIcon;
@@ -0,0 +1,63 @@
1
+ const DiffIcon = props => (
2
+ <svg
3
+ width="2em"
4
+ height="2em"
5
+ viewBox="0 0 256 256"
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ >
8
+ <rect fill="none" height="256" width="256" />
9
+ <circle
10
+ cx="196"
11
+ cy="188"
12
+ fill="none"
13
+ r="28"
14
+ stroke="#000"
15
+ strokeLinecap="round"
16
+ strokeLinejoin="round"
17
+ strokeWidth="12"
18
+ />
19
+ <path
20
+ d="M196,160V119.9a48.2,48.2,0,0,0-14.1-34L144,48"
21
+ fill="none"
22
+ stroke="#000"
23
+ strokeLinecap="round"
24
+ strokeLinejoin="round"
25
+ strokeWidth="12"
26
+ />
27
+ <polyline
28
+ fill="none"
29
+ points="144 88 144 48 184 48"
30
+ stroke="#000"
31
+ strokeLinecap="round"
32
+ strokeLinejoin="round"
33
+ strokeWidth="12"
34
+ />
35
+ <circle
36
+ cx="60"
37
+ cy="68"
38
+ fill="none"
39
+ r="28"
40
+ stroke="#000"
41
+ strokeLinecap="round"
42
+ strokeLinejoin="round"
43
+ strokeWidth="12"
44
+ />
45
+ <path
46
+ d="M60,96v40.1a48.2,48.2,0,0,0,14.1,34L112,208"
47
+ fill="none"
48
+ stroke="#000"
49
+ strokeLinecap="round"
50
+ strokeLinejoin="round"
51
+ strokeWidth="12"
52
+ />
53
+ <polyline
54
+ fill="none"
55
+ points="112 168 112 208 72 208"
56
+ stroke="#000"
57
+ strokeLinecap="round"
58
+ strokeLinejoin="round"
59
+ strokeWidth="12"
60
+ />
61
+ </svg>
62
+ );
63
+ export default DiffIcon;
@@ -0,0 +1,20 @@
1
+ const EyeIcon = props => (
2
+ <svg
3
+ className="feather feather-eye"
4
+ fill="none"
5
+ height="24"
6
+ width="24"
7
+ viewBox="0 0 24 24"
8
+ stroke="currentColor"
9
+ strokeWidth="2"
10
+ strokeLinecap="round"
11
+ strokeLinejoin="round"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ {...props}
14
+ >
15
+ <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
16
+ <circle cx="12" cy="12" r="3" />
17
+ </svg>
18
+ );
19
+
20
+ export default EyeIcon;
@@ -0,0 +1,7 @@
1
+ const FilterIcon = props => (
2
+ <svg height="23px" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
3
+ <path d="M20 36h8v-4h-8v4zm-14-24v4h36v-4h-36zm6 14h24v-4h-24v4z" />
4
+ <path d="M0 0h48v48h-48z" fill="none" />
5
+ </svg>
6
+ );
7
+ export default FilterIcon;
@@ -0,0 +1,25 @@
1
+ const JupyterExportIcon = props => (
2
+ <svg
3
+ className="feather feather-jupyter-export"
4
+ fill="none"
5
+ height="24"
6
+ width="24"
7
+ viewBox="0 0 24 24"
8
+ stroke="currentColor"
9
+ strokeWidth="2"
10
+ strokeLinecap="round"
11
+ strokeLinejoin="round"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ {...props}
14
+ >
15
+ {/* Notebook outline */}
16
+ <rect x="3" y="2" width="14" height="20" rx="2" ry="2" />
17
+
18
+ {/* Notebook lines */}
19
+ <line x1="7" y1="6" x2="13" y2="6" />
20
+ <line x1="7" y1="10" x2="13" y2="10" />
21
+ <line x1="7" y1="14" x2="13" y2="14" />
22
+ </svg>
23
+ );
24
+
25
+ export default JupyterExportIcon;
@@ -1,14 +1,14 @@
1
1
  import '../../styles/loading.css';
2
2
 
3
- export default function LoadingIcon() {
4
- return (
5
- <center>
6
- <div class="lds-ring">
7
- <div></div>
8
- <div></div>
9
- <div></div>
10
- <div></div>
11
- </div>
12
- </center>
3
+ export default function LoadingIcon({ centered = true }) {
4
+ const content = (
5
+ <div className="lds-ring">
6
+ <div></div>
7
+ <div></div>
8
+ <div></div>
9
+ <div></div>
10
+ </div>
13
11
  );
12
+
13
+ return centered ? <center>{content}</center> : content;
14
14
  }
@@ -1,51 +1,13 @@
1
1
  const PythonIcon = props => (
2
2
  <svg
3
- width="45px"
4
- height="45px"
5
- viewBox="0 0 64 64"
6
- fill="none"
7
3
  xmlns="http://www.w3.org/2000/svg"
4
+ x="0px"
5
+ y="0px"
6
+ width="24"
7
+ height="24"
8
+ viewBox="0 0 48 48"
8
9
  >
9
- <g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
10
- <g
11
- id="SVGRepo_tracerCarrier"
12
- strokeLinecap="round"
13
- strokeLinejoin="round"
14
- ></g>
15
- <g id="SVGRepo_iconCarrier">
16
- <path
17
- d="M31.885 16c-8.124 0-7.617 3.523-7.617 3.523l.01 3.65h7.752v1.095H21.197S16 23.678 16 31.876c0 8.196 4.537 7.906 4.537 7.906h2.708v-3.804s-.146-4.537 4.465-4.537h7.688s4.32.07 4.32-4.175v-7.019S40.374 16 31.885 16zm-4.275 2.454c.771 0 1.395.624 1.395 1.395s-.624 1.395-1.395 1.395a1.393 1.393 0 0 1-1.395-1.395c0-.771.624-1.395 1.395-1.395z"
18
- fill="url(#a)"
19
- ></path>
20
- <path
21
- d="M32.115 47.833c8.124 0 7.617-3.523 7.617-3.523l-.01-3.65H31.97v-1.095h10.832S48 40.155 48 31.958c0-8.197-4.537-7.906-4.537-7.906h-2.708v3.803s.146 4.537-4.465 4.537h-7.688s-4.32-.07-4.32 4.175v7.019s-.656 4.247 7.833 4.247zm4.275-2.454a1.393 1.393 0 0 1-1.395-1.395c0-.77.624-1.394 1.395-1.394s1.395.623 1.395 1.394c0 .772-.624 1.395-1.395 1.395z"
22
- fill="url(#b)"
23
- ></path>
24
- <defs>
25
- <linearGradient
26
- id="a"
27
- x1="19.075"
28
- y1="18.782"
29
- x2="34.898"
30
- y2="34.658"
31
- gradientUnits="userSpaceOnUse"
32
- >
33
- <stop stopColor="#387EB8"></stop>
34
- <stop offset="1" stopColor="#366994"></stop>
35
- </linearGradient>
36
- <linearGradient
37
- id="b"
38
- x1="28.809"
39
- y1="28.882"
40
- x2="45.803"
41
- y2="45.163"
42
- gradientUnits="userSpaceOnUse"
43
- >
44
- <stop stopColor="#FFE052"></stop>
45
- <stop offset="1" stopColor="#FFC331"></stop>
46
- </linearGradient>
47
- </defs>
48
- </g>
10
+ <path d="M 24 3 C 20.271429 3 18.240267 3.9470561 16.792969 4.5742188 L 16.791016 4.5742188 C 15.488673 5.1421213 14.704771 6.3187748 14.365234 7.4296875 C 14.025697 8.5406002 14 9.6506515 14 10.640625 L 14 14 L 10.640625 14 C 9.6506515 14 8.5406002 14.0257 7.4296875 14.365234 C 6.3187748 14.704771 5.1421212 15.488626 4.5742188 16.791016 L 4.5742188 16.792969 C 3.947056 18.24022 3 20.271429 3 24 C 3 27.728571 3.9470561 29.759733 4.5742188 31.207031 L 4.5742188 31.208984 C 5.1421212 32.511374 6.3187748 33.295229 7.4296875 33.634766 C 8.5406002 33.974256 9.6506515 34 10.640625 34 L 14 34 L 14 37.359375 C 14 38.349349 14.0257 39.459401 14.365234 40.570312 C 14.704771 41.681225 15.488626 42.857879 16.791016 43.425781 L 16.792969 43.425781 C 18.24022 44.052944 20.271429 45 24 45 C 27.728571 45 29.759733 44.052944 31.207031 43.425781 L 31.208984 43.425781 C 32.511374 42.857879 33.295229 41.681225 33.634766 40.570312 C 33.974256 39.459401 34 38.349349 34 37.359375 L 34 34 L 37.359375 34 C 38.349349 34 39.459401 33.9743 40.570312 33.634766 C 41.681225 33.295229 42.857879 32.511374 43.425781 31.208984 L 43.425781 31.207031 C 44.052944 29.75978 45 27.728571 45 24 C 45 20.271429 44.052944 18.240267 43.425781 16.792969 L 43.425781 16.791016 C 42.857879 15.488673 41.681225 14.704771 40.570312 14.365234 C 39.459401 14.025697 38.349349 14 37.359375 14 L 34 14 L 34 10.640625 C 34 9.6506515 33.974303 8.5406002 33.634766 7.4296875 C 33.295229 6.3187748 32.511374 5.1421213 31.208984 4.5742188 L 31.207031 4.5742188 C 29.75978 3.9470561 27.728571 3 24 3 z M 24 6 C 27.268623 6 28.459017 6.6519922 30.009766 7.3242188 C 30.427376 7.5063161 30.590162 7.7325533 30.765625 8.3066406 C 30.941088 8.8807279 31 9.7405985 31 10.640625 L 31 15.253906 A 1.50015 1.50015 0 0 0 31 15.740234 L 31 19 C 31 20.950062 29.450062 22.5 27.5 22.5 L 20.5 22.5 C 16.928062 22.5 14 25.428062 14 29 L 14 31 L 10.640625 31 C 9.7405985 31 8.8807279 30.941088 8.3066406 30.765625 C 7.7325533 30.590162 7.5063162 30.427376 7.3242188 30.009766 C 6.6519921 28.459017 6 27.268623 6 24 C 6 20.731377 6.6519921 19.540983 7.3242188 17.990234 C 7.5063161 17.572624 7.7325533 17.409838 8.3066406 17.234375 C 8.8807279 17.058912 9.7405985 17 10.640625 17 L 23.5 17 A 1.50015 1.50015 0 1 0 23.5 14 L 17 14 L 17 10.640625 C 17 9.7405985 17.058912 8.8807279 17.234375 8.3066406 C 17.409838 7.7325533 17.572624 7.5063162 17.990234 7.3242188 C 19.540983 6.6519921 20.731377 6 24 6 z M 20.5 9 A 1.5 1.5 0 0 0 20.5 12 A 1.5 1.5 0 0 0 20.5 9 z M 34 17 L 37.359375 17 C 38.259401 17 39.119272 17.05891 39.693359 17.234375 C 40.267447 17.409838 40.493684 17.572624 40.675781 17.990234 C 41.348008 19.540983 42 20.731377 42 24 C 42 27.268623 41.348008 28.459017 40.675781 30.009766 C 40.493684 30.427376 40.267447 30.590162 39.693359 30.765625 C 39.119272 30.941088 38.259401 31 37.359375 31 L 24.5 31 A 1.50015 1.50015 0 1 0 24.5 34 L 31 34 L 31 37.359375 C 31 38.259401 30.94109 39.119272 30.765625 39.693359 C 30.590162 40.267447 30.427376 40.493684 30.009766 40.675781 C 28.459017 41.348008 27.268623 42 24 42 C 20.731377 42 19.540983 41.348008 17.990234 40.675781 C 17.572624 40.493684 17.409838 40.267447 17.234375 39.693359 C 17.058912 39.119272 17 38.259401 17 37.359375 L 17 29 C 17 27.049938 18.549938 25.5 20.5 25.5 L 27.5 25.5 C 31.071938 25.5 34 22.571938 34 19 L 34 17 z M 27.5 36 A 1.5 1.5 0 0 0 27.5 39 A 1.5 1.5 0 0 0 27.5 36 z"></path>
49
11
  </svg>
50
12
  );
51
13
 
package/src/app/index.tsx CHANGED
@@ -9,7 +9,9 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom';
9
9
 
10
10
  import { NamespacePage } from './pages/NamespacePage/Loadable';
11
11
  import { NodePage } from './pages/NodePage/Loadable';
12
+ import RevisionDiff from './pages/NodePage/RevisionDiff';
12
13
  import { SQLBuilderPage } from './pages/SQLBuilderPage/Loadable';
14
+ import { CubeBuilderPage } from './pages/CubeBuilderPage/Loadable';
13
15
  import { TagPage } from './pages/TagPage/Loadable';
14
16
  import { AddEditNodePage } from './pages/AddEditNodePage/Loadable';
15
17
  import { AddEditTagPage } from './pages/AddEditTagPage/Loadable';
@@ -52,6 +54,16 @@ export function App() {
52
54
  key="edit"
53
55
  element={<AddEditNodePage />}
54
56
  />
57
+ <Route
58
+ path=":name/edit-cube"
59
+ key="edit-cube"
60
+ element={<CubeBuilderPage />}
61
+ />
62
+ <Route
63
+ path=":name/revisions/:revision"
64
+ element={<RevisionDiff />}
65
+ />
66
+ <Route path=":name/:tab" element={<NodePage />} />
55
67
  </Route>
56
68
 
57
69
  <Route path="/" element={<NamespacePage />} key="index" />
@@ -72,6 +84,18 @@ export function App() {
72
84
  key="register"
73
85
  element={<RegisterTablePage />}
74
86
  ></Route>
87
+ <Route path="/create/cube">
88
+ <Route
89
+ path=":initialNamespace"
90
+ key="create"
91
+ element={<CubeBuilderPage />}
92
+ />
93
+ <Route
94
+ path=""
95
+ key="create"
96
+ element={<CubeBuilderPage />}
97
+ />
98
+ </Route>
75
99
  <Route path="create/:nodeType">
76
100
  <Route
77
101
  path=":initialNamespace"
@@ -0,0 +1,10 @@
1
+ import AlertIcon from '../../icons/AlertIcon';
2
+
3
+ export const AlertMessage = ({ message }) => {
4
+ return (
5
+ <div className="message alert">
6
+ <AlertIcon />
7
+ {message}
8
+ </div>
9
+ );
10
+ };
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Component for selecting node columns based on the current form state
3
+ */
4
+ import { ErrorMessage, useFormikContext } from 'formik';
5
+ import { useContext, useMemo, useState, useEffect } from 'react';
6
+ import DJClientContext from '../../providers/djclient';
7
+ import { FormikSelect } from './FormikSelect';
8
+
9
+ export const ColumnsSelect = ({
10
+ defaultValue,
11
+ fieldName,
12
+ label,
13
+ labelStyle = {},
14
+ isMulti = false,
15
+ isClearable = true,
16
+ }) => {
17
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
18
+
19
+ // Used to pull out current form values for node validation
20
+ const { values } = useFormikContext();
21
+
22
+ // The available columns, determined from validating the node query
23
+ const [availableColumns, setAvailableColumns] = useState([]);
24
+ const selectableOptions = useMemo(() => {
25
+ if (availableColumns && availableColumns.length > 0) {
26
+ return availableColumns;
27
+ }
28
+ }, [availableColumns]);
29
+
30
+ // Fetch columns by validating the latest node query
31
+ const fetchColumns = async () => {
32
+ try {
33
+ const { status, json } = await djClient.validateNode(
34
+ values.type,
35
+ values.name,
36
+ values.display_name,
37
+ values.description,
38
+ values.query,
39
+ );
40
+ if (json?.columns) {
41
+ setAvailableColumns(
42
+ json.columns.map(col => ({ value: col.name, label: col.name })),
43
+ );
44
+ }
45
+ } catch (error) {
46
+ console.error('Error fetching columns:', error);
47
+ }
48
+ };
49
+
50
+ useEffect(() => {
51
+ fetchColumns();
52
+ }, [values.type, values.name, values.query]);
53
+
54
+ return (
55
+ <div className="CubeCreationInput">
56
+ <ErrorMessage name={fieldName} component="span" />
57
+ <label htmlFor={fieldName} style={labelStyle}>
58
+ {label}
59
+ </label>
60
+ <span data-testid={`select-${fieldName}`}>
61
+ <FormikSelect
62
+ className={isMulti ? 'MultiSelectInput' : 'SelectInput'}
63
+ defaultValue={
64
+ isMulti
65
+ ? defaultValue.map(val => {
66
+ return {
67
+ value: val,
68
+ label: val,
69
+ };
70
+ })
71
+ : defaultValue
72
+ ? { value: defaultValue, label: defaultValue }
73
+ : null
74
+ }
75
+ selectOptions={selectableOptions}
76
+ formikFieldName={fieldName}
77
+ onFocus={event => fetchColumns(event)}
78
+ isMulti={isMulti}
79
+ isClearable={isClearable}
80
+ />
81
+ </span>
82
+ </div>
83
+ );
84
+ };
@@ -0,0 +1,17 @@
1
+ import { ErrorMessage, Field } from 'formik';
2
+
3
+ export const DescriptionField = () => {
4
+ return (
5
+ <div className="DescriptionInput NodeCreationInput">
6
+ <ErrorMessage name="description" component="span" />
7
+ <label htmlFor="Description">Description</label>
8
+ <Field
9
+ type="textarea"
10
+ as="textarea"
11
+ name="description"
12
+ id="Description"
13
+ placeholder="Describe your node"
14
+ />
15
+ </div>
16
+ );
17
+ };
@@ -0,0 +1,16 @@
1
+ import { ErrorMessage, Field } from 'formik';
2
+
3
+ export const DisplayNameField = () => {
4
+ return (
5
+ <div className="DisplayNameInput NodeCreationInput">
6
+ <ErrorMessage name="display_name" component="span" />
7
+ <label htmlFor="displayName">Display Name *</label>
8
+ <Field
9
+ type="text"
10
+ name="display_name"
11
+ id="displayName"
12
+ placeholder="Human readable display name"
13
+ />
14
+ </div>
15
+ );
16
+ };
@@ -12,6 +12,8 @@ export const FormikSelect = ({
12
12
  style,
13
13
  className = 'SelectInput',
14
14
  isMulti = false,
15
+ isClearable = false,
16
+ onFocus = event => {},
15
17
  }) => {
16
18
  // eslint-disable-next-line no-unused-vars
17
19
  const [field, _, helpers] = useField(formikFieldName);
@@ -37,6 +39,9 @@ export const FormikSelect = ({
37
39
  onChange={selected => setValue(getValue(selected))}
38
40
  styles={style}
39
41
  isMulti={isMulti}
42
+ isClearable={isClearable}
43
+ onFocus={event => onFocus(event)}
44
+ id={field.name}
40
45
  />
41
46
  );
42
47
  };
@@ -11,12 +11,13 @@ export const FullNameField = props => {
11
11
 
12
12
  useEffect(() => {
13
13
  // Set the value of the node's full name based on its namespace and display name
14
- if (values.namespace && values.display_name) {
14
+ if (values.namespace || values.display_name) {
15
15
  setFieldValue(
16
16
  props.name,
17
17
  `${values.namespace}.${values.display_name
18
18
  .toLowerCase()
19
- .replace(/ /g, '_')}` || '',
19
+ .replace(/ /g, '_')
20
+ .replace(/[^a-zA-Z0-9_]/g, '')}` || '',
20
21
  );
21
22
  }
22
23
  }, [setFieldValue, props.name, values]);