datajunction-ui 0.0.1-a94 → 0.0.1-a95

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.1a94",
3
+ "version": "0.0.1a95",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -67,7 +67,9 @@ describe('AddEditNodePage submission failed', () => {
67
67
 
68
68
  it('for editing a node', async () => {
69
69
  const mockDjClient = initializeMockDJClient();
70
- mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
70
+ mockDjClient.DataJunctionAPI.getNodeForEditing.mockReturnValue(
71
+ mocks.mockGetMetricNode,
72
+ );
71
73
  mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
72
74
  status: 500,
73
75
  json: { message: 'Update failed' },
@@ -152,7 +152,9 @@ describe('AddEditNodePage submission succeeded', () => {
152
152
  it('for editing a transform or dimension node', async () => {
153
153
  const mockDjClient = initializeMockDJClient();
154
154
 
155
- mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockTransformNode);
155
+ mockDjClient.DataJunctionAPI.getNodeForEditing.mockReturnValue(
156
+ mocks.mockGetTransformNode,
157
+ );
156
158
  mockDjClient.DataJunctionAPI.patchNode = jest.fn();
157
159
  mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
158
160
  status: 201,
@@ -189,9 +191,9 @@ describe('AddEditNodePage submission succeeded', () => {
189
191
  'SELECT repair_order_id, municipality_id, hard_hat_id, dispatcher_id FROM default.repair_orders',
190
192
  'published',
191
193
  [],
192
- undefined,
193
- undefined,
194
- undefined,
194
+ '',
195
+ '',
196
+ '',
195
197
  undefined,
196
198
  );
197
199
  expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledTimes(1);
@@ -212,8 +214,9 @@ describe('AddEditNodePage submission succeeded', () => {
212
214
  it('for editing a metric node', async () => {
213
215
  const mockDjClient = initializeMockDJClient();
214
216
 
215
- mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
216
- mockDjClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
217
+ mockDjClient.DataJunctionAPI.getNodeForEditing.mockReturnValue(
218
+ mocks.mockGetMetricNode,
219
+ );
217
220
  mockDjClient.DataJunctionAPI.patchNode = jest.fn();
218
221
  mockDjClient.DataJunctionAPI.patchNode.mockReturnValue({
219
222
  status: 201,
@@ -249,10 +252,10 @@ describe('AddEditNodePage submission succeeded', () => {
249
252
  'Number of repair orders!!!',
250
253
  'SELECT count(repair_order_id) FROM default.repair_orders',
251
254
  'published',
252
- [],
255
+ ['repair_order_id', 'country'],
253
256
  'neutral',
254
257
  'unitless',
255
- 4,
258
+ 5,
256
259
  undefined,
257
260
  );
258
261
  expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledTimes(1);
@@ -35,6 +35,7 @@ export const initializeMockDJClient = () => {
35
35
  ];
36
36
  },
37
37
  metrics: {},
38
+ getNodeForEditing: jest.fn(),
38
39
  namespaces: () => {
39
40
  return [
40
41
  {
@@ -146,7 +147,9 @@ describe('AddEditNodePage', () => {
146
147
 
147
148
  it('Edit node page renders with the selected node', async () => {
148
149
  const mockDjClient = initializeMockDJClient();
149
- mockDjClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
150
+ mockDjClient.DataJunctionAPI.getNodeForEditing.mockReturnValue(
151
+ mocks.mockGetMetricNode,
152
+ );
150
153
 
151
154
  const element = testElement(mockDjClient);
152
155
  renderEditNode(element);
@@ -175,17 +178,12 @@ describe('AddEditNodePage', () => {
175
178
 
176
179
  it('Verify edit page node not found', async () => {
177
180
  const mockDjClient = initializeMockDJClient();
178
- mockDjClient.DataJunctionAPI.node = jest.fn();
179
- mockDjClient.DataJunctionAPI.node.mockReturnValue({
180
- message: 'A node with name `default.num_repair_orders` does not exist.',
181
- errors: [],
182
- warnings: [],
183
- });
181
+ mockDjClient.DataJunctionAPI.getNodeForEditing.mockReturnValue(null);
184
182
  const element = testElement(mockDjClient);
185
183
  renderEditNode(element);
186
184
 
187
185
  await waitFor(() => {
188
- expect(mockDjClient.DataJunctionAPI.node).toBeCalledTimes(1);
186
+ expect(mockDjClient.DataJunctionAPI.getNodeForEditing).toBeCalledTimes(1);
189
187
  expect(
190
188
  screen.getByText('Node default.num_repair_orders does not exist!'),
191
189
  ).toBeInTheDocument();
@@ -194,18 +192,14 @@ describe('AddEditNodePage', () => {
194
192
 
195
193
  it('Verify only transforms, metrics, and dimensions can be edited', async () => {
196
194
  const mockDjClient = initializeMockDJClient();
197
- mockDjClient.DataJunctionAPI.node = jest.fn();
198
- mockDjClient.DataJunctionAPI.node.mockReturnValue({
199
- namespace: 'default',
200
- type: 'source',
201
- name: 'default.repair_orders',
202
- display_name: 'Default: Repair Orders',
203
- });
195
+ mockDjClient.DataJunctionAPI.getNodeForEditing.mockReturnValue(
196
+ mocks.mockGetSourceNode,
197
+ );
204
198
  const element = testElement(mockDjClient);
205
199
  renderEditNode(element);
206
200
 
207
201
  await waitFor(() => {
208
- expect(mockDjClient.DataJunctionAPI.node).toBeCalledTimes(1);
202
+ expect(mockDjClient.DataJunctionAPI.getNodeForEditing).toBeCalledTimes(1);
209
203
  expect(
210
204
  screen.getByText(
211
205
  'Node default.num_repair_orders is of type source and cannot be edited',
@@ -188,26 +188,35 @@ export function AddEditNodePage({ extensions = {} }) {
188
188
  };
189
189
 
190
190
  const getExistingNodeData = async name => {
191
- const data = await djClient.node(name);
192
- if (data.type === 'metric') {
193
- const metric = await djClient.metric(name);
194
- data.upstream_node = metric.upstream_node;
195
- data.expression = metric.expression;
196
- data.required_dimensions = metric.required_dimensions;
191
+ const node = await djClient.getNodeForEditing(name);
192
+ if (node === null) {
193
+ return { message: `Node ${name} does not exist` };
197
194
  }
198
- return data;
199
- };
195
+ const baseData = {
196
+ name: node.name,
197
+ type: node.type.toLowerCase(),
198
+ display_name: node.current.displayName,
199
+ description: node.current.description,
200
+ primary_key: node.current.primaryKey,
201
+ query: node.current.query,
202
+ tags: node.tags,
203
+ mode: node.current.mode.toLowerCase(),
204
+ };
200
205
 
201
- const primaryKeyFromNode = node => {
202
- return node.columns
203
- .filter(
204
- col =>
205
- col.attributes &&
206
- col.attributes.filter(
207
- attr => attr.attribute_type.name === 'primary_key',
208
- ).length > 0,
209
- )
210
- .map(col => col.name);
206
+ if (node.type === 'METRIC') {
207
+ return {
208
+ ...baseData,
209
+ metric_direction: node.current.metricMetadata?.direction?.toLowerCase(),
210
+ metric_unit: node.current.metricMetadata?.unit?.name?.toLowerCase(),
211
+ significant_digits: node.current.metricMetadata?.significantDigits,
212
+ required_dimensions: node.current.requiredDimensions.map(
213
+ dim => dim.name,
214
+ ),
215
+ upstream_node: node.current.parents[0]?.name,
216
+ aggregate_expression: node.current.metricMetadata?.expression,
217
+ };
218
+ }
219
+ return baseData;
211
220
  };
212
221
 
213
222
  const runValidityChecks = (data, setNode, setMessage) => {
@@ -217,7 +226,6 @@ export function AddEditNodePage({ extensions = {} }) {
217
226
  setMessage(`Node ${name} does not exist!`);
218
227
  return;
219
228
  }
220
-
221
229
  // Check if node type can be edited
222
230
  if (!nodeCanBeEdited(data.type)) {
223
231
  setNode(null);
@@ -246,14 +254,14 @@ export function AddEditNodePage({ extensions = {} }) {
246
254
  'primary_key',
247
255
  'mode',
248
256
  'tags',
249
- 'expression',
257
+ 'aggregate_expression',
250
258
  'upstream_node',
259
+ 'metric_unit',
260
+ 'metric_direction',
261
+ 'significant_digits',
251
262
  ];
252
- const primaryKey = primaryKeyFromNode(data);
253
263
  fields.forEach(field => {
254
- if (field === 'primary_key') {
255
- setFieldValue(field, primaryKey);
256
- } else if (field === 'tags') {
264
+ if (field === 'tags') {
257
265
  setFieldValue(
258
266
  field,
259
267
  data[field].map(tag => tag.name),
@@ -262,27 +270,6 @@ export function AddEditNodePage({ extensions = {} }) {
262
270
  setFieldValue(field, data[field] || '', false);
263
271
  }
264
272
  });
265
- if (data.metric_metadata?.direction) {
266
- setFieldValue('metric_direction', data.metric_metadata.direction);
267
- }
268
- if (data.metric_metadata?.unit) {
269
- setFieldValue(
270
- 'metric_unit',
271
- data.metric_metadata.unit.name.toLowerCase(),
272
- );
273
- }
274
- if (data.metric_metadata?.significant_digits) {
275
- setFieldValue(
276
- 'significant_digits',
277
- data.metric_metadata.significant_digits,
278
- );
279
- }
280
- if (data.expression) {
281
- setFieldValue('aggregate_expression', data.expression);
282
- }
283
- if (data.upstream_node) {
284
- setFieldValue('upstream_node', data.upstream_node);
285
- }
286
273
  setNode(data);
287
274
 
288
275
  // For react-select fields, we have to explicitly set the entire
@@ -290,13 +277,13 @@ export function AddEditNodePage({ extensions = {} }) {
290
277
  setSelectTags(
291
278
  <TagsField
292
279
  defaultValue={data.tags.map(t => {
293
- return { value: t.name, label: t.display_name };
280
+ return { value: t.name, label: t.displayName };
294
281
  })}
295
282
  />,
296
283
  );
297
284
  setSelectPrimaryKey(
298
285
  <ColumnsSelect
299
- defaultValue={primaryKey}
286
+ defaultValue={data.primary_key}
300
287
  fieldName="primary_key"
301
288
  label="Primary Key"
302
289
  isMulti={true}
@@ -409,7 +396,11 @@ export function AddEditNodePage({ extensions = {} }) {
409
396
  {nodeType === 'metric' || node.type === 'metric' ? (
410
397
  <MetricQueryField
411
398
  djClient={djClient}
412
- value={node.expression ? node.expression : ''}
399
+ value={
400
+ node.aggregate_expression
401
+ ? node.aggregate_expression
402
+ : ''
403
+ }
413
404
  />
414
405
  ) : (
415
406
  <NodeQueryField
@@ -181,7 +181,6 @@ export default function NodeInfoTab({ node }) {
181
181
  : 'None'}
182
182
  </p>
183
183
  </div>
184
- {console.log('node?.metric_metadata', node?.metric_metadata)}
185
184
  <div style={{ marginRight: '2rem' }}>
186
185
  <h6 className="mb-0 w-100">Significant Digits</h6>
187
186
  <p
@@ -128,6 +128,59 @@ export const DataJunctionAPI = {
128
128
  return data;
129
129
  },
130
130
 
131
+ getNodeForEditing: async function (name) {
132
+ const query = `
133
+ query GetNodeForEditing($name: String!) {
134
+ findNodes (names: [$name]) {
135
+ name
136
+ type
137
+ current {
138
+ displayName
139
+ description
140
+ primaryKey
141
+ query
142
+ parents { name }
143
+ metricMetadata {
144
+ direction
145
+ unit { name }
146
+ expression
147
+ significantDigits
148
+ incompatibleDruidFunctions
149
+ }
150
+ requiredDimensions {
151
+ name
152
+ }
153
+ mode
154
+ }
155
+ tags {
156
+ name
157
+ displayName
158
+ }
159
+ }
160
+ }
161
+ `;
162
+
163
+ const results = await (
164
+ await fetch(DJ_GQL, {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Content-Type': 'application/json',
168
+ },
169
+ credentials: 'include',
170
+ body: JSON.stringify({
171
+ query,
172
+ variables: {
173
+ name: name,
174
+ },
175
+ }),
176
+ })
177
+ ).json();
178
+ if (results.data.findNodes.length === 0) {
179
+ return null;
180
+ }
181
+ return results.data.findNodes[0];
182
+ },
183
+
131
184
  getMetric: async function (name) {
132
185
  const query = `
133
186
  query GetMetric($name: String!) {
@@ -287,6 +287,73 @@ export const mocks = {
287
287
  aggregate_expression: 'count(repair_order_id)',
288
288
  required_dimensions: [],
289
289
  },
290
+
291
+ mockGetSourceNode: {
292
+ name: 'default.num_repair_orders',
293
+ type: 'SOURCE',
294
+ current: {
295
+ displayName: 'source.prodhive.dse.playback_f',
296
+ description:
297
+ 'This source node was automatically created as a registered table.',
298
+ primaryKey: [],
299
+ parents: [],
300
+ metricMetadata: null,
301
+ requiredDimensions: [],
302
+ mode: 'PUBLISHED',
303
+ },
304
+ tags: [],
305
+ },
306
+
307
+ mockGetMetricNode: {
308
+ name: 'default.num_repair_orders',
309
+ type: 'METRIC',
310
+ current: {
311
+ displayName: 'Default: Num Repair Orders',
312
+ description: 'Number of repair orders',
313
+ primaryKey: ['repair_order_id', 'country'],
314
+ query:
315
+ 'SELECT count(repair_order_id) default_DOT_num_repair_orders FROM default.repair_orders',
316
+ parents: [
317
+ {
318
+ name: 'default.repair_orders',
319
+ },
320
+ ],
321
+ metricMetadata: {
322
+ direction: 'NEUTRAL',
323
+ unit: {
324
+ name: 'UNITLESS',
325
+ },
326
+ expression: 'count(repair_order_id)',
327
+ significantDigits: 5,
328
+ incompatibleDruidFunctions: ['IF'],
329
+ },
330
+ requiredDimensions: [],
331
+ mode: 'PUBLISHED',
332
+ },
333
+ tags: [{ name: 'purpose', displayName: 'Purpose' }],
334
+ },
335
+
336
+ mockGetTransformNode: {
337
+ name: 'default.repair_order_transform',
338
+ type: 'TRANSFORM',
339
+ current: {
340
+ displayName: 'Default: Repair Order Transform',
341
+ description: 'Repair order dimension',
342
+ primaryKey: [],
343
+ query:
344
+ 'SELECT repair_order_id, municipality_id, hard_hat_id, dispatcher_id FROM default.repair_orders',
345
+ parents: [
346
+ {
347
+ name: 'default.repair_orders',
348
+ },
349
+ ],
350
+ metricMetadata: null,
351
+ requiredDimensions: [],
352
+ mode: 'PUBLISHED',
353
+ },
354
+ tags: [],
355
+ },
356
+
290
357
  attributes: [
291
358
  {
292
359
  uniqueness_scope: [],