datajunction-ui 0.0.14 → 0.0.16

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.
@@ -2,12 +2,12 @@ import { useEffect, useState } from 'react';
2
2
  import * as React from 'react';
3
3
  import EditColumnPopover from './EditColumnPopover';
4
4
  import EditColumnDescriptionPopover from './EditColumnDescriptionPopover';
5
- import LinkDimensionPopover from './LinkDimensionPopover';
5
+ import ManageDimensionLinksDialog from './ManageDimensionLinksDialog';
6
+ import AddComplexDimensionLinkPopover from './AddComplexDimensionLinkPopover';
6
7
  import { labelize } from '../../../utils/form';
7
8
  import PartitionColumnPopover from './PartitionColumnPopover';
8
9
  import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
9
10
  import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
10
- import { link } from 'fs';
11
11
 
12
12
  export default function NodeColumnTab({ node, djClient }) {
13
13
  const [attributes, setAttributes] = useState([]);
@@ -85,18 +85,29 @@ export default function NodeColumnTab({ node, djClient }) {
85
85
 
86
86
  const columnList = columns => {
87
87
  return columns?.map(col => {
88
- const dimensionLinks = (links.length > 0 ? links : node?.dimension_links)
89
- .map(link => [
90
- link.dimension.name,
91
- Object.entries(link.foreign_keys).filter(
92
- entry => entry[0] === node.name + '.' + col.name,
93
- ),
94
- ])
95
- .filter(keys => keys[1].length >= 1);
96
- const referencedDimensionNode =
97
- dimensionLinks.length > 0 ? dimensionLinks[0][0] : null;
88
+ // FK Links: Only show links that specifically reference THIS column's foreign keys
89
+ // Filter out complex dimension links that may join on multiple columns
90
+ const fkLinksForColumn = (
91
+ links.length > 0 ? links : node?.dimension_links
92
+ )
93
+ .filter(link => {
94
+ // Check if this link has a foreign key entry for this specific column
95
+ const foreignKeys = Object.keys(link.foreign_keys || {});
96
+ const columnKey = `${node.name}.${col.name}`;
97
+ return foreignKeys.includes(columnKey);
98
+ })
99
+ .map(link => link.dimension.name);
100
+
101
+ // Check if this column has a reference dimension link
102
+ const referenceLink = col.dimension
103
+ ? {
104
+ dimension: col.dimension.name,
105
+ dimension_column: col.dimension_column,
106
+ role: col.dimension.role,
107
+ }
108
+ : null;
98
109
  return (
99
- <tr key={col.name}>
110
+ <tr key={col.name} className="column-row">
100
111
  <td
101
112
  className="text-start"
102
113
  role="columnheader"
@@ -146,28 +157,60 @@ export default function NodeColumnTab({ node, djClient }) {
146
157
  </span>
147
158
  </td>
148
159
  {node.type !== 'cube' ? (
149
- <td>
150
- {dimensionLinks.length > 0
151
- ? dimensionLinks.map(link => (
152
- <span
153
- className="rounded-pill badge bg-secondary-soft"
154
- style={{ fontSize: '14px' }}
155
- key={link[0]}
156
- >
157
- <a href={`/nodes/${link[0]}`}>{link[0]}</a>
158
- </span>
159
- ))
160
- : ''}
161
- <LinkDimensionPopover
162
- column={col}
163
- dimensionNodes={dimensionLinks.map(link => link[0])}
164
- node={node}
165
- options={dimensions}
166
- onSubmit={async () => {
167
- const res = await djClient.node(node.name);
168
- setLinks(res.dimension_links);
160
+ <td className="dimension-links-cell">
161
+ <div
162
+ style={{
163
+ display: 'flex',
164
+ alignItems: 'center',
165
+ flexWrap: 'wrap',
166
+ gap: '0.25rem',
167
+ position: 'relative',
169
168
  }}
170
- />
169
+ >
170
+ {fkLinksForColumn.length > 0 && (
171
+ <div
172
+ style={{
173
+ display: 'flex',
174
+ flexWrap: 'wrap',
175
+ gap: '0.25rem',
176
+ }}
177
+ >
178
+ {fkLinksForColumn.map(dimName => (
179
+ <span
180
+ className="rounded-pill badge bg-secondary-soft dimension-badge"
181
+ style={{ fontSize: '14px', position: 'relative' }}
182
+ key={dimName}
183
+ title="FK Link (via primary key)"
184
+ >
185
+ <a href={`/nodes/${dimName}`}>{dimName}</a>
186
+ </span>
187
+ ))}
188
+ </div>
189
+ )}
190
+ {referenceLink && (
191
+ <span
192
+ className="rounded-pill badge bg-info dimension-badge"
193
+ style={{ fontSize: '14px', position: 'relative' }}
194
+ title={`Reference Link: ${referenceLink.dimension}.${referenceLink.dimension_column}`}
195
+ >
196
+ <a href={`/nodes/${referenceLink.dimension}`}>
197
+ {referenceLink.dimension}.{referenceLink.dimension_column}
198
+ </a>
199
+ </span>
200
+ )}
201
+ <ManageDimensionLinksDialog
202
+ column={col}
203
+ node={node}
204
+ dimensions={dimensions}
205
+ fkLinks={fkLinksForColumn}
206
+ referenceLink={referenceLink}
207
+ onSubmit={async () => {
208
+ const res = await djClient.node(node.name);
209
+ setLinks(res.dimension_links);
210
+ setColumns(res.columns);
211
+ }}
212
+ />
213
+ </div>
171
214
  </td>
172
215
  ) : (
173
216
  ''
@@ -206,6 +249,24 @@ export default function NodeColumnTab({ node, djClient }) {
206
249
 
207
250
  return (
208
251
  <>
252
+ <style>
253
+ {`
254
+ .dimension-link-edit:hover {
255
+ opacity: 1 !important;
256
+ color: #007bff !important;
257
+ }
258
+ .dimension-badge a {
259
+ max-width: 300px;
260
+ display: inline-block;
261
+ overflow: hidden;
262
+ text-overflow: ellipsis;
263
+ white-space: nowrap;
264
+ vertical-align: bottom;
265
+ direction: rtl;
266
+ text-align: left;
267
+ }
268
+ `}
269
+ </style>
209
270
  <div className="table-responsive">
210
271
  <table className="card-inner-table table">
211
272
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
@@ -216,7 +277,7 @@ export default function NodeColumnTab({ node, djClient }) {
216
277
  <th>Type</th>
217
278
  {node?.type !== 'cube' ? (
218
279
  <>
219
- <th>Linked Dimension</th>
280
+ <th>Dimension Links</th>
220
281
  <th>Attributes</th>
221
282
  </>
222
283
  ) : (
@@ -229,39 +290,143 @@ export default function NodeColumnTab({ node, djClient }) {
229
290
  </table>
230
291
  </div>
231
292
  <div>
232
- <h3>Linked Dimensions (Custom Join SQL)</h3>
293
+ <div
294
+ style={{
295
+ display: 'flex',
296
+ alignItems: 'center',
297
+ justifyContent: 'space-between',
298
+ marginBottom: '1rem',
299
+ }}
300
+ >
301
+ <h3 style={{ margin: 0 }}>
302
+ Complex Dimension Links (Custom Join SQL)
303
+ </h3>
304
+ <AddComplexDimensionLinkPopover
305
+ node={node}
306
+ dimensions={dimensions}
307
+ onSubmit={async () => {
308
+ const res = await djClient.node(node.name);
309
+ setLinks(res.dimension_links);
310
+ }}
311
+ />
312
+ </div>
233
313
  <table className="card-inner-table table">
234
314
  <thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
235
315
  <tr>
236
316
  <th className="text-start">Dimension Node</th>
237
317
  <th>Join Type</th>
238
318
  <th>Join SQL</th>
319
+ <th>Join Cardinality</th>
239
320
  <th>Role</th>
321
+ <th>Actions</th>
240
322
  </tr>
241
323
  </thead>
242
324
  <tbody>
243
- {node?.dimension_links.map(link => {
244
- return (
245
- <tr key={link.dimension.name}>
246
- <td>
247
- <a href={'/nodes/' + link.dimension.name}>
248
- {link.dimension.name}
249
- </a>
250
- </td>
251
- <td>{link.join_type.toUpperCase()}</td>
252
- <td style={{ width: '25rem', maxWidth: 'none' }}>
253
- <SyntaxHighlighter
254
- language="sql"
255
- style={foundation}
256
- wrapLongLines={true}
257
- >
258
- {link.join_sql}
259
- </SyntaxHighlighter>
260
- </td>
261
- <td>{link.role}</td>
262
- </tr>
263
- );
264
- })}
325
+ {node?.dimension_links && node.dimension_links.length > 0 ? (
326
+ node.dimension_links.map((link, idx) => {
327
+ return (
328
+ <tr key={`${link.dimension.name}-${link.role || idx}`}>
329
+ <td>
330
+ <a href={'/nodes/' + link.dimension.name}>
331
+ {link.dimension.name}
332
+ </a>
333
+ </td>
334
+ <td>{link.join_type.toUpperCase()}</td>
335
+ <td style={{ maxWidth: '400px' }}>
336
+ <SyntaxHighlighter
337
+ language="sql"
338
+ style={foundation}
339
+ wrapLongLines={true}
340
+ >
341
+ {link.join_sql?.trim()}
342
+ </SyntaxHighlighter>
343
+ </td>
344
+ <td>
345
+ {link.join_cardinality
346
+ ? link.join_cardinality.replace(/_/g, ' ').toUpperCase()
347
+ : 'N/A'}
348
+ </td>
349
+ <td>{link.role || '-'}</td>
350
+ <td>
351
+ <div style={{ display: 'flex', gap: '0.5rem' }}>
352
+ <AddComplexDimensionLinkPopover
353
+ node={node}
354
+ dimensions={dimensions}
355
+ existingLink={link}
356
+ isEditMode={true}
357
+ onSubmit={async () => {
358
+ const res = await djClient.node(node.name);
359
+ setLinks(res.dimension_links);
360
+ }}
361
+ />
362
+ <button
363
+ onClick={async () => {
364
+ if (
365
+ window.confirm(
366
+ `Remove link to ${link.dimension.name}?`,
367
+ )
368
+ ) {
369
+ try {
370
+ const response =
371
+ await djClient.removeComplexDimensionLink(
372
+ node.name,
373
+ link.dimension.name,
374
+ link.role || null,
375
+ );
376
+ if (
377
+ response.status === 200 ||
378
+ response.status === 201 ||
379
+ response.status === 204
380
+ ) {
381
+ alert(
382
+ 'Complex dimension link removed successfully!',
383
+ );
384
+ window.location.reload();
385
+ } else {
386
+ console.error('Remove link error:', response);
387
+ alert(
388
+ response.json?.message ||
389
+ `Failed to remove link (status: ${response.status})`,
390
+ );
391
+ }
392
+ } catch (error) {
393
+ console.error('Remove link exception:', error);
394
+ alert(`Error removing link: ${error.message}`);
395
+ }
396
+ }
397
+ }}
398
+ style={{
399
+ padding: '0.25rem 0.5rem',
400
+ fontSize: '0.75rem',
401
+ background: '#dc3545',
402
+ color: 'white',
403
+ border: 'none',
404
+ borderRadius: '4px',
405
+ cursor: 'pointer',
406
+ }}
407
+ >
408
+ Remove
409
+ </button>
410
+ </div>
411
+ </td>
412
+ </tr>
413
+ );
414
+ })
415
+ ) : (
416
+ <tr>
417
+ <td
418
+ colSpan="6"
419
+ style={{
420
+ textAlign: 'center',
421
+ padding: '2rem',
422
+ color: '#6c757d',
423
+ }}
424
+ >
425
+ No complex dimension links. Click the + button above to add
426
+ one.
427
+ </td>
428
+ </tr>
429
+ )}
265
430
  </tbody>
266
431
  </table>
267
432
  </div>