datajunction-ui 0.0.31 → 0.0.34
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.
- package/TODO.md +265 -0
- package/package.json +1 -1
- package/src/app/components/ListGroupItem.jsx +2 -2
- package/src/app/components/QueryInfo.jsx +2 -1
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +2 -2
- package/src/app/components/djgraph/__tests__/Collapse.test.jsx +6 -3
- package/src/app/pages/AddEditNodePage/index.jsx +1 -1
- package/src/app/pages/AddEditTagPage/index.jsx +1 -1
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +55 -21
- package/src/app/pages/NodePage/NodeInfoTab.jsx +17 -6
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +5 -0
- package/src/app/pages/NodePage/NodePreAggregationsTab.jsx +656 -0
- package/src/app/pages/NodePage/NodeValidateTab.jsx +4 -2
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +58 -45
- package/src/app/pages/NodePage/__tests__/NodePreAggregationsTab.test.jsx +654 -0
- package/src/app/pages/NodePage/index.jsx +9 -1
- package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +19 -4
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +47 -9
- package/src/app/pages/SQLBuilderPage/index.jsx +2 -2
- package/src/app/services/DJService.js +26 -0
- package/src/styles/preaggregations.css +547 -0
package/TODO.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# UI Test Improvements TODO
|
|
2
|
+
|
|
3
|
+
## Phase 1: Fix Test Warnings (Completed)
|
|
4
|
+
|
|
5
|
+
### 1.1 Fix `act()` Warnings ✅
|
|
6
|
+
|
|
7
|
+
- [x] Wrap async state updates in `waitFor()` properly
|
|
8
|
+
- [x] Add proper cleanup/waiting in tests for components with async effects
|
|
9
|
+
- [x] Fixed: SQLBuilderPage, NotificationsPage, CubeBuilderPage tests
|
|
10
|
+
- [ ] Remaining: Some AddEditNodePage/AddEditTagPage warnings (minor, from Formik internals)
|
|
11
|
+
|
|
12
|
+
### 1.2 Fix Key Prop Warnings ✅
|
|
13
|
+
|
|
14
|
+
- [x] Fix `key={rowValue}` in SQLBuilderPage (line 185) - use index-based key
|
|
15
|
+
- [x] Fix `key={rowValue}` in NodeValidateTab (line 346) - use index-based key
|
|
16
|
+
- [x] Fix missing key in QueryInfo.jsx error mapping (line 125)
|
|
17
|
+
- [x] Fix incorrect mock data in Collapse.test.jsx causing undefined keys
|
|
18
|
+
- [x] Fix missing key in NodeInfoTab.jsx: nodeTags, primary_key, required_dimensions, owners
|
|
19
|
+
|
|
20
|
+
### 1.3 Fix DOM/React Warnings ✅
|
|
21
|
+
|
|
22
|
+
- [x] Fix `value` prop on `textarea` should not be null - changed `custom_metadata: null` to `''` in AddEditNodePage
|
|
23
|
+
- [x] Fix invalid DOM property `class` -> `className` in AddEditTagPage
|
|
24
|
+
- [x] Fix nested elements: changed `<div>` to `<span>` in NodeInfoTab nodeTags
|
|
25
|
+
- [x] Fix nested `<p>` tags: changed outer `<p>` to `<div>` in ListGroupItem (Markdown can render `<p>`)
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Phase 2: GraphQL Type Safety (In Progress)
|
|
30
|
+
|
|
31
|
+
### Goal
|
|
32
|
+
|
|
33
|
+
Migrate all REST GET endpoints to GraphQL for:
|
|
34
|
+
|
|
35
|
+
- Field selection (fetch only what's needed)
|
|
36
|
+
- Type safety via GraphQL codegen
|
|
37
|
+
- Single source of truth for read operations
|
|
38
|
+
|
|
39
|
+
Keep REST for mutations (POST/PATCH/DELETE) since they're already implemented and mutations are simpler.
|
|
40
|
+
|
|
41
|
+
### Completed ✅
|
|
42
|
+
|
|
43
|
+
#### 2.1 Type Generation Setup
|
|
44
|
+
|
|
45
|
+
- [x] Added `@graphql-codegen/cli`, `@graphql-codegen/typescript`, `@graphql-codegen/typescript-operations`
|
|
46
|
+
- [x] Added `openapi-typescript` for REST type generation
|
|
47
|
+
- [x] Created `codegen.yml` pointing to static `schema.graphql` (no live server needed)
|
|
48
|
+
- [x] Added scripts: `yarn codegen`, `yarn codegen:graphql`, `yarn codegen:openapi`, `yarn codegen:check`
|
|
49
|
+
- [x] Generated types:
|
|
50
|
+
- `src/types/graphql.ts` (GraphQL types + query operation types)
|
|
51
|
+
- `src/types/openapi.ts` (REST endpoint types)
|
|
52
|
+
- [x] Server Makefile updated with `make generate-schemas` and `make check-schemas`
|
|
53
|
+
|
|
54
|
+
#### 2.2 GraphQL Query Files
|
|
55
|
+
|
|
56
|
+
- [x] Created `src/graphql/queries/` directory with 10 `.graphql` files:
|
|
57
|
+
- `listNodesForLanding.graphql`
|
|
58
|
+
- `listCubesForPreset.graphql`
|
|
59
|
+
- `cubeForPlanner.graphql`
|
|
60
|
+
- `getNodeForEditing.graphql`
|
|
61
|
+
- `getNodesByNames.graphql`
|
|
62
|
+
- `getMetric.graphql`
|
|
63
|
+
- `getCubeForEditing.graphql`
|
|
64
|
+
- `upstreamNodes.graphql`
|
|
65
|
+
- `downstreamNodes.graphql`
|
|
66
|
+
- `getNodeColumnsWithPartitions.graphql`
|
|
67
|
+
|
|
68
|
+
#### 2.3 Typed GraphQL Service
|
|
69
|
+
|
|
70
|
+
- [x] Created `src/app/services/DJGraphQLService.ts` - fully typed GraphQL service
|
|
71
|
+
- [x] All 10 existing GraphQL methods are now typed
|
|
72
|
+
- [x] DJService.js GraphQL methods now delegate to DJGraphQLService.ts
|
|
73
|
+
- [x] Components continue to use djClient context pattern (no changes needed)
|
|
74
|
+
|
|
75
|
+
### Usage
|
|
76
|
+
|
|
77
|
+
Components continue to use the existing djClient pattern - no changes needed:
|
|
78
|
+
|
|
79
|
+
```jsx
|
|
80
|
+
// Existing pattern (still works, now backed by typed service)
|
|
81
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
82
|
+
const node = await djClient.getNodeForEditing('my.node');
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For direct typed access (optional):
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { DJGraphQLService } from '../services/DJGraphQLService';
|
|
89
|
+
import type { GetNodeForEditingQuery } from '../../types/graphql';
|
|
90
|
+
|
|
91
|
+
const node = await DJGraphQLService.getNodeForEditing('my.node');
|
|
92
|
+
// node.name, node.type, node.current.displayName are all typed
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Development Workflow
|
|
96
|
+
|
|
97
|
+
**Schema files (committed to repo):**
|
|
98
|
+
| File | Source |
|
|
99
|
+
|------|--------|
|
|
100
|
+
| `openapi.json` (monorepo root) | Server OpenAPI spec |
|
|
101
|
+
| `datajunction-server/.../schema.graphql` | Server GraphQL schema |
|
|
102
|
+
| `datajunction-ui/src/types/graphql.ts` | Generated GraphQL types |
|
|
103
|
+
| `datajunction-ui/src/types/openapi.ts` | Generated REST types |
|
|
104
|
+
|
|
105
|
+
**When you change the server API:**
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
cd datajunction-server
|
|
109
|
+
make generate-schemas # Regenerates openapi.json + schema.graphql
|
|
110
|
+
git add ../openapi.json datajunction_server/api/graphql/schema.graphql
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**When you change UI GraphQL queries or pull server changes:**
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
cd datajunction-ui
|
|
117
|
+
yarn codegen # Regenerates graphql.ts + openapi.ts
|
|
118
|
+
git add src/types/
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**CI validation:**
|
|
122
|
+
|
|
123
|
+
- Server: `make check` includes `make check-schemas` → fails if schemas are stale
|
|
124
|
+
- UI: `yarn codegen:check` → fails if generated types are stale
|
|
125
|
+
|
|
126
|
+
### Current State
|
|
127
|
+
|
|
128
|
+
**Typed GraphQL methods (in DJGraphQLService.ts):**
|
|
129
|
+
| Method | Purpose |
|
|
130
|
+
|--------|---------|
|
|
131
|
+
| `listNodesForLanding` | Paginated node listing |
|
|
132
|
+
| `listCubesForPreset` | Cube dropdown |
|
|
133
|
+
| `cubeForPlanner` | Query planner page |
|
|
134
|
+
| `getNodeForEditing` | Edit node form |
|
|
135
|
+
| `getNodesByNames` | Batch node fetch |
|
|
136
|
+
| `getMetric` | Metric details |
|
|
137
|
+
| `getCubeForEditing` | Edit cube form |
|
|
138
|
+
| `upstreamNodes` | Upstream nodes |
|
|
139
|
+
| `downstreamNodes` | Downstream nodes |
|
|
140
|
+
| `getNodeColumnsWithPartitions` | Column partitions |
|
|
141
|
+
|
|
142
|
+
### REST GET Endpoints to Migrate (~45 methods)
|
|
143
|
+
|
|
144
|
+
#### High Priority (core functionality, frequently used)
|
|
145
|
+
|
|
146
|
+
- [ ] `node` - GET /nodes/{name}/ → Full node details page
|
|
147
|
+
- [ ] `nodes` - GET /nodes/?prefix= → Node listings
|
|
148
|
+
- [ ] `nodesWithType` - GET /nodes/?node_type= → Filtered node lists
|
|
149
|
+
- [ ] `metric` - GET /metrics/{name}/ → Metric details (REST version)
|
|
150
|
+
- [ ] `metrics` - GET /metrics/ → All metrics list
|
|
151
|
+
- [ ] `cube` - GET /cubes/{name}/ → Full cube details
|
|
152
|
+
- [ ] `commonDimensions` - GET /metrics/common/dimensions/ → Dimension lookups
|
|
153
|
+
- [ ] `upstreams` - GET /nodes/{name}/upstream/ → (has GQL version, deprecate REST)
|
|
154
|
+
- [ ] `downstreams` - GET /nodes/{name}/downstream/ → (has GQL version, deprecate REST)
|
|
155
|
+
- [ ] `history` - GET /history?node= → Activity history
|
|
156
|
+
- [ ] `revisions` - GET /nodes/{name}/revisions/ → Node revision history
|
|
157
|
+
|
|
158
|
+
#### Medium Priority (namespace/catalog/tags)
|
|
159
|
+
|
|
160
|
+
- [ ] `namespaces` - GET /namespaces/ → Namespace list
|
|
161
|
+
- [ ] `namespace` - GET /namespaces/{nmspce} → Namespace details
|
|
162
|
+
- [ ] `namespaceSources` - GET /namespaces/{namespace}/sources
|
|
163
|
+
- [ ] `catalogs` - GET /catalogs → Catalog list
|
|
164
|
+
- [ ] `engines` - GET /engines → Engine list
|
|
165
|
+
- [ ] `dimensions` - GET /dimensions → All dimensions
|
|
166
|
+
- [ ] `nodeDimensions` - GET /nodes/{nodeName}/dimensions
|
|
167
|
+
- [ ] `nodesWithDimension` - GET /dimensions/{name}/nodes/
|
|
168
|
+
- [ ] `attributes` - GET /attributes
|
|
169
|
+
- [ ] `listTags` - GET /tags → Tag list
|
|
170
|
+
- [ ] `getTag` - GET /tags/{tagName} → Tag details
|
|
171
|
+
- [ ] `listNodesForTag` - GET /tags/{tagName}/nodes
|
|
172
|
+
- [ ] `users` - GET /users?with_activity=true
|
|
173
|
+
|
|
174
|
+
#### Medium Priority (materializations)
|
|
175
|
+
|
|
176
|
+
- [ ] `materializations` - GET /nodes/{node}/materializations
|
|
177
|
+
- [ ] `availabilityStates` - GET /nodes/{node}/availability/
|
|
178
|
+
- [ ] `materializationInfo` - GET /materialization/info
|
|
179
|
+
- [ ] `listPreaggs` - GET /preaggs/
|
|
180
|
+
- [ ] `getPreagg` - GET /preaggs/{preaggId}
|
|
181
|
+
- [ ] `getCubeDetails` - GET /cubes/{cubeName}
|
|
182
|
+
|
|
183
|
+
#### Lower Priority (SQL/data - may need special handling)
|
|
184
|
+
|
|
185
|
+
- [ ] `sql` - GET /sql/{metric_name}?... → Generated SQL
|
|
186
|
+
- [ ] `sqls` - GET /sql/?metrics=&dimensions= → Multi-metric SQL
|
|
187
|
+
- [ ] `measuresV3` - GET /sql/measures/v3/ → V3 measures SQL
|
|
188
|
+
- [ ] `metricsV3` - GET /sql/metrics/v3/ → V3 metrics SQL
|
|
189
|
+
- [ ] `data` - GET /data/?metrics=&dimensions= → Query execution
|
|
190
|
+
- [ ] `nodeData` - GET /data/{nodeName}? → Node data query
|
|
191
|
+
- [ ] `compiledSql` - GET /sql/{node}/ → Compiled SQL
|
|
192
|
+
|
|
193
|
+
#### Lower Priority (system metrics/admin)
|
|
194
|
+
|
|
195
|
+
- [ ] `querySystemMetric` - GET /system/data/{metric} → Dashboard analytics
|
|
196
|
+
- [ ] `system.node_counts_by_*` - System metrics
|
|
197
|
+
- [ ] `system.dimensions` - GET /system/dimensions
|
|
198
|
+
- [ ] `nodeDetails` - GET /nodes/details/
|
|
199
|
+
- [ ] `node_dag` - GET /nodes/{name}/dag/
|
|
200
|
+
- [ ] `node_lineage` - GET /nodes/{name}/lineage/
|
|
201
|
+
- [ ] `listDeployments` - GET /deployments
|
|
202
|
+
- [ ] `listMetricMetadata` - GET /metrics/metadata
|
|
203
|
+
|
|
204
|
+
#### Lower Priority (notifications/export)
|
|
205
|
+
|
|
206
|
+
- [ ] `getNotificationPreferences` - GET /notifications/
|
|
207
|
+
- [ ] `getSubscribedHistory` - GET /history/?only_subscribed=true
|
|
208
|
+
- [ ] `listServiceAccounts` - GET /service-accounts
|
|
209
|
+
- [ ] `clientCode` - GET /datajunction-clients/python/new_node/{name}
|
|
210
|
+
- [ ] `notebookExportCube` - GET /datajunction-clients/python/notebook/?cube=
|
|
211
|
+
- [ ] `notebookExportNamespace` - GET /datajunction-clients/python/notebook/?namespace=
|
|
212
|
+
|
|
213
|
+
#### Keep as REST (streaming - EventSource)
|
|
214
|
+
|
|
215
|
+
- `stream` - EventSource /stream/... (GraphQL subscriptions are different tech)
|
|
216
|
+
- `streamNodeData` - EventSource /stream/{nodeName}...
|
|
217
|
+
|
|
218
|
+
### Implementation Tasks
|
|
219
|
+
|
|
220
|
+
1. **Server-side: Extend GraphQL schema**
|
|
221
|
+
|
|
222
|
+
- [ ] Ensure all required fields are available in GraphQL schema
|
|
223
|
+
- [ ] Add any missing queries to the GraphQL API
|
|
224
|
+
- [ ] Verify introspection is enabled for codegen
|
|
225
|
+
|
|
226
|
+
2. **UI-side: Set up GraphQL codegen**
|
|
227
|
+
|
|
228
|
+
- [ ] Add `@graphql-codegen/cli` and `@graphql-codegen/typescript` as dev dependencies
|
|
229
|
+
- [ ] Create `codegen.yml` configuration
|
|
230
|
+
- [ ] Add npm script: `yarn generate-types`
|
|
231
|
+
- [ ] Generate types to `src/types/graphql.ts`
|
|
232
|
+
|
|
233
|
+
3. **UI-side: Migrate DJService methods**
|
|
234
|
+
|
|
235
|
+
- [ ] Start with high-priority methods
|
|
236
|
+
- [ ] Create GraphQL queries in `src/graphql/queries/`
|
|
237
|
+
- [ ] Update DJService.js methods to use GraphQL
|
|
238
|
+
- [ ] Remove deprecated REST calls
|
|
239
|
+
|
|
240
|
+
4. **Type the mutation payloads**
|
|
241
|
+
- [ ] Keep OpenAPI types for REST mutations
|
|
242
|
+
- [ ] Add `openapi-typescript` for mutation request/response types
|
|
243
|
+
- [ ] Generate to `src/types/api-mutations.ts`
|
|
244
|
+
|
|
245
|
+
### Benefits
|
|
246
|
+
|
|
247
|
+
- **Field selection**: UI fetches only needed data
|
|
248
|
+
- **Type safety**: Compile-time errors when code doesn't match schema
|
|
249
|
+
- **Single type source for reads**: GraphQL schema is authoritative
|
|
250
|
+
- **Performance**: Already proven with `cubeForPlanner`, `listNodesForLanding`
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Phase 3: Integration Tests (Long-term)
|
|
255
|
+
|
|
256
|
+
### Goal
|
|
257
|
+
|
|
258
|
+
Add integration test suite that runs against a real DJ server to catch real integration issues.
|
|
259
|
+
|
|
260
|
+
### Tasks
|
|
261
|
+
|
|
262
|
+
- [ ] Add Docker Compose setup for test DJ server
|
|
263
|
+
- [ ] Create separate test suite for integration tests
|
|
264
|
+
- [ ] Add CI workflow to run integration tests
|
|
265
|
+
- [ ] Consider using Playwright or Cypress for E2E tests
|
package/package.json
CHANGED
|
@@ -9,14 +9,14 @@ export default class ListGroupItem extends Component {
|
|
|
9
9
|
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
10
10
|
<div>
|
|
11
11
|
<h6 className="mb-0 w-100">{label}</h6>
|
|
12
|
-
<
|
|
12
|
+
<div
|
|
13
13
|
className="mb-0 opacity-75"
|
|
14
14
|
role="dialog"
|
|
15
15
|
aria-hidden="false"
|
|
16
16
|
aria-label={label}
|
|
17
17
|
>
|
|
18
18
|
<Markdown>{value}</Markdown>
|
|
19
|
-
</
|
|
19
|
+
</div>
|
|
20
20
|
</div>
|
|
21
21
|
</div>
|
|
22
22
|
</div>
|
|
@@ -122,8 +122,9 @@ export default function QueryInfo({
|
|
|
122
122
|
<li className={'query-info'}>
|
|
123
123
|
<label>Logs</label>{' '}
|
|
124
124
|
{errors?.length ? (
|
|
125
|
-
errors.map(error => (
|
|
125
|
+
errors.map((error, idx) => (
|
|
126
126
|
<div
|
|
127
|
+
key={`error-${idx}`}
|
|
127
128
|
style={{
|
|
128
129
|
height: '800px',
|
|
129
130
|
width: '80%',
|
|
@@ -13,7 +13,7 @@ exports[`<ListGroupItem /> should render and match the snapshot 1`] = `
|
|
|
13
13
|
>
|
|
14
14
|
Name
|
|
15
15
|
</h6>
|
|
16
|
-
<
|
|
16
|
+
<div
|
|
17
17
|
aria-hidden="false"
|
|
18
18
|
aria-label="Name"
|
|
19
19
|
className="mb-0 opacity-75"
|
|
@@ -24,7 +24,7 @@ exports[`<ListGroupItem /> should render and match the snapshot 1`] = `
|
|
|
24
24
|
Something
|
|
25
25
|
</span>
|
|
26
26
|
</Markdown>
|
|
27
|
-
</
|
|
27
|
+
</div>
|
|
28
28
|
</div>
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
@@ -35,10 +35,13 @@ describe('<Collapse />', () => {
|
|
|
35
35
|
<Collapse
|
|
36
36
|
{...defaultProps}
|
|
37
37
|
data={{
|
|
38
|
+
name: 'test.transform',
|
|
38
39
|
type: 'transform',
|
|
39
|
-
column_names: Array(11
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
column_names: Array.from({ length: 11 }, (_, idx) => ({
|
|
41
|
+
name: `column_${idx}`,
|
|
42
|
+
type: 'string',
|
|
43
|
+
order: idx,
|
|
44
|
+
})),
|
|
42
45
|
primary_key: [],
|
|
43
46
|
}}
|
|
44
47
|
/>
|
|
@@ -185,30 +185,48 @@ describe('CubeBuilderPage', () => {
|
|
|
185
185
|
jest.clearAllMocks();
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
it('renders without crashing', () => {
|
|
188
|
+
it('renders without crashing', async () => {
|
|
189
189
|
render(
|
|
190
190
|
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
191
191
|
<CubeBuilderPage />
|
|
192
192
|
</DJClientContext.Provider>,
|
|
193
193
|
);
|
|
194
|
+
|
|
195
|
+
// Wait for async effects to complete
|
|
196
|
+
await waitFor(() => {
|
|
197
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
198
|
+
});
|
|
199
|
+
|
|
194
200
|
expect(screen.getByText('Cube')).toBeInTheDocument();
|
|
195
201
|
});
|
|
196
202
|
|
|
197
|
-
it('renders the Metrics section', () => {
|
|
203
|
+
it('renders the Metrics section', async () => {
|
|
198
204
|
render(
|
|
199
205
|
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
200
206
|
<CubeBuilderPage />
|
|
201
207
|
</DJClientContext.Provider>,
|
|
202
208
|
);
|
|
209
|
+
|
|
210
|
+
// Wait for async effects to complete
|
|
211
|
+
await waitFor(() => {
|
|
212
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
213
|
+
});
|
|
214
|
+
|
|
203
215
|
expect(screen.getByText('Metrics *')).toBeInTheDocument();
|
|
204
216
|
});
|
|
205
217
|
|
|
206
|
-
it('renders the Dimensions section', () => {
|
|
218
|
+
it('renders the Dimensions section', async () => {
|
|
207
219
|
render(
|
|
208
220
|
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
209
221
|
<CubeBuilderPage />
|
|
210
222
|
</DJClientContext.Provider>,
|
|
211
223
|
);
|
|
224
|
+
|
|
225
|
+
// Wait for async effects to complete
|
|
226
|
+
await waitFor(() => {
|
|
227
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
228
|
+
});
|
|
229
|
+
|
|
212
230
|
expect(screen.getByText('Dimensions *')).toBeInTheDocument();
|
|
213
231
|
});
|
|
214
232
|
|
|
@@ -237,22 +255,31 @@ describe('CubeBuilderPage', () => {
|
|
|
237
255
|
}
|
|
238
256
|
fireEvent.click(screen.getAllByText('Dimensions *')[0]);
|
|
239
257
|
|
|
240
|
-
|
|
258
|
+
// Wait for commonDimensions to be called and state to update
|
|
259
|
+
await waitFor(() => {
|
|
260
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
261
|
+
});
|
|
241
262
|
|
|
242
263
|
const selectDimensions = screen.getAllByTestId('select-dimensions')[0];
|
|
243
264
|
expect(selectDimensions).toBeDefined();
|
|
244
265
|
expect(selectDimensions).not.toBeNull();
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
266
|
+
|
|
267
|
+
await waitFor(() => {
|
|
268
|
+
expect(
|
|
269
|
+
screen.getByText(
|
|
270
|
+
'default.repair_order_details.repair_order_id → default.repair_order.hard_hat_id → default.hard_hat.birth_date',
|
|
271
|
+
),
|
|
272
|
+
).toBeInTheDocument();
|
|
273
|
+
});
|
|
250
274
|
|
|
251
275
|
const selectDimensionsDate = screen.getAllByTestId(
|
|
252
276
|
'dimensions-default.date_dim',
|
|
253
277
|
)[0];
|
|
254
278
|
|
|
255
279
|
fireEvent.keyDown(selectDimensionsDate.firstChild, { key: 'ArrowDown' });
|
|
280
|
+
await waitFor(() => {
|
|
281
|
+
expect(screen.getByText('Day')).toBeInTheDocument();
|
|
282
|
+
});
|
|
256
283
|
fireEvent.click(screen.getByText('Day'));
|
|
257
284
|
fireEvent.click(screen.getByText('Month'));
|
|
258
285
|
fireEvent.click(screen.getByText('Year'));
|
|
@@ -264,9 +291,8 @@ describe('CubeBuilderPage', () => {
|
|
|
264
291
|
})[0];
|
|
265
292
|
expect(createCube).toBeInTheDocument();
|
|
266
293
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
294
|
+
fireEvent.click(createCube);
|
|
295
|
+
|
|
270
296
|
await waitFor(() => {
|
|
271
297
|
expect(mockDjClient.createCube).toHaveBeenCalledWith(
|
|
272
298
|
'',
|
|
@@ -322,22 +348,31 @@ describe('CubeBuilderPage', () => {
|
|
|
322
348
|
|
|
323
349
|
fireEvent.click(screen.getAllByText('Dimensions *')[0]);
|
|
324
350
|
|
|
325
|
-
|
|
351
|
+
// Wait for commonDimensions to be called and state to update
|
|
352
|
+
await waitFor(() => {
|
|
353
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
354
|
+
});
|
|
326
355
|
|
|
327
356
|
const selectDimensions = screen.getAllByTestId('select-dimensions')[0];
|
|
328
357
|
expect(selectDimensions).toBeDefined();
|
|
329
358
|
expect(selectDimensions).not.toBeNull();
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
359
|
+
|
|
360
|
+
await waitFor(() => {
|
|
361
|
+
expect(
|
|
362
|
+
screen.getByText(
|
|
363
|
+
'default.repair_order_details.repair_order_id → default.repair_order.hard_hat_id → default.hard_hat.birth_date',
|
|
364
|
+
),
|
|
365
|
+
).toBeInTheDocument();
|
|
366
|
+
});
|
|
335
367
|
|
|
336
368
|
const selectDimensionsDate = screen.getAllByTestId(
|
|
337
369
|
'dimensions-default.date_dim',
|
|
338
370
|
)[0];
|
|
339
371
|
|
|
340
372
|
fireEvent.keyDown(selectDimensionsDate.firstChild, { key: 'ArrowDown' });
|
|
373
|
+
await waitFor(() => {
|
|
374
|
+
expect(screen.getByText('Day')).toBeInTheDocument();
|
|
375
|
+
});
|
|
341
376
|
fireEvent.click(screen.getByText('Day'));
|
|
342
377
|
fireEvent.click(screen.getByText('Month'));
|
|
343
378
|
fireEvent.click(screen.getByText('Year'));
|
|
@@ -349,9 +384,8 @@ describe('CubeBuilderPage', () => {
|
|
|
349
384
|
})[0];
|
|
350
385
|
expect(createCube).toBeInTheDocument();
|
|
351
386
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
});
|
|
387
|
+
fireEvent.click(createCube);
|
|
388
|
+
|
|
355
389
|
await waitFor(() => {
|
|
356
390
|
expect(mockDjClient.patchCube).toHaveBeenCalledWith(
|
|
357
391
|
'default.repair_orders_cube',
|
|
@@ -37,9 +37,13 @@ export default function NodeInfoTab({ node }) {
|
|
|
37
37
|
const [metricInfo, setMetricInfo] = useState(null);
|
|
38
38
|
|
|
39
39
|
const nodeTags = node?.tags.map(tag => (
|
|
40
|
-
<
|
|
40
|
+
<span
|
|
41
|
+
key={tag.name}
|
|
42
|
+
className={'badge tag_value'}
|
|
43
|
+
style={{ marginRight: '4px' }}
|
|
44
|
+
>
|
|
41
45
|
<a href={`/tags/${tag.name}`}>{tag.display_name}</a>
|
|
42
|
-
</
|
|
46
|
+
</span>
|
|
43
47
|
));
|
|
44
48
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
45
49
|
|
|
@@ -314,13 +318,19 @@ export default function NodeInfoTab({ node }) {
|
|
|
314
318
|
}
|
|
315
319
|
>
|
|
316
320
|
{node?.type !== 'metric'
|
|
317
|
-
? node?.primary_key?.map(dim => (
|
|
318
|
-
<span
|
|
321
|
+
? node?.primary_key?.map((dim, idx) => (
|
|
322
|
+
<span
|
|
323
|
+
key={`pk-${idx}`}
|
|
324
|
+
className="rounded-pill badge bg-secondary-soft PrimaryKey"
|
|
325
|
+
>
|
|
319
326
|
<a href={`/nodes/${node?.name}`}>{dim}</a>
|
|
320
327
|
</span>
|
|
321
328
|
))
|
|
322
|
-
: node?.required_dimensions?.map(dim => (
|
|
323
|
-
<span
|
|
329
|
+
: node?.required_dimensions?.map((dim, idx) => (
|
|
330
|
+
<span
|
|
331
|
+
key={`rd-${idx}`}
|
|
332
|
+
className="rounded-pill badge bg-secondary-soft PrimaryKey"
|
|
333
|
+
>
|
|
324
334
|
<a href={`/nodes/${node?.upstream_node}`}>{dim.name}</a>
|
|
325
335
|
</span>
|
|
326
336
|
))}
|
|
@@ -341,6 +351,7 @@ export default function NodeInfoTab({ node }) {
|
|
|
341
351
|
<p className="mb-0 opacity-75">
|
|
342
352
|
{node?.owners.map(owner => (
|
|
343
353
|
<span
|
|
354
|
+
key={owner.username}
|
|
344
355
|
className="badge node_type__transform"
|
|
345
356
|
style={{ margin: '2px', fontSize: '16px', cursor: 'pointer' }}
|
|
346
357
|
>
|
|
@@ -11,6 +11,11 @@ import AvailabilityStateBlock from './AvailabilityStateBlock';
|
|
|
11
11
|
|
|
12
12
|
const cronstrue = require('cronstrue');
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Cube materialization tab - shows cube-specific materializations.
|
|
16
|
+
* For non-cube nodes, the parent component (index.jsx) renders
|
|
17
|
+
* NodePreAggregationsTab instead.
|
|
18
|
+
*/
|
|
14
19
|
export default function NodeMaterializationTab({ node, djClient }) {
|
|
15
20
|
const [rawMaterializations, setRawMaterializations] = useState([]);
|
|
16
21
|
const [selectedRevisionTab, setSelectedRevisionTab] = useState(null);
|