datajunction-ui
Version:
DataJunction UI
156 lines (149 loc) • 4.76 kB
JSX
import { useEffect, useState } from 'react';
import * as React from 'react';
import { labelize } from '../../../utils/form';
import LoadingIcon from '../../icons/LoadingIcon';
export default function NodeDependenciesTab({ node, djClient }) {
const [upstreams, setUpstreams] = useState(null);
const [downstreams, setDownstreams] = useState(null);
const [dimensions, setDimensions] = useState(null);
useEffect(() => {
if (node) {
// Reset state when node changes
setUpstreams(null);
setDownstreams(null);
setDimensions(null);
// Load all three in parallel, display each as it loads
djClient.upstreamsGQL(node.name).then(setUpstreams).catch(console.error);
djClient
.downstreamsGQL(node.name)
.then(setDownstreams)
.catch(console.error);
djClient
.nodeDimensions(node.name)
.then(setDimensions)
.catch(console.error);
}
}, [djClient, node]);
return (
<div>
<h2>Upstreams</h2>
{upstreams !== null ? (
<NodeList nodes={upstreams} />
) : (
<span style={{ display: 'block' }}>
<LoadingIcon centered={false} />
</span>
)}
<h2>Downstreams</h2>
{downstreams !== null ? (
<NodeList nodes={downstreams} />
) : (
<span style={{ display: 'block' }}>
<LoadingIcon centered={false} />
</span>
)}
<h2>Dimensions</h2>
{dimensions !== null ? (
<NodeDimensionsList rawDimensions={dimensions} />
) : (
<span style={{ display: 'block' }}>
<LoadingIcon centered={false} />
</span>
)}
</div>
);
}
export function NodeDimensionsList({ rawDimensions }) {
const dimensions = Object.entries(
rawDimensions.reduce((group, dimension) => {
group[dimension.node_name + dimension.path] =
group[dimension.node_name + dimension.path] ?? [];
group[dimension.node_name + dimension.path].push(dimension);
return group;
}, {}),
);
return (
<div style={{ padding: '0.5rem' }}>
{dimensions.map(grouping => {
const dimensionsInGroup = grouping[1];
const role = dimensionsInGroup[0].path
.map(pathItem => pathItem.split('.').slice(-1))
.join(' → ');
const fullPath = dimensionsInGroup[0].path.join(' → ');
const groupHeader = (
<span
style={{
fontWeight: 'normal',
marginBottom: '15px',
marginTop: '15px',
}}
>
<a href={`/nodes/${dimensionsInGroup[0].node_name}`}>
<b>{dimensionsInGroup[0].node_display_name}</b>
</a>{' '}
with role{' '}
<span className="HighlightPath">
<b>{role}</b>
</span>{' '}
via <span className="HighlightPath">{fullPath}</span>
</span>
);
const dimensionGroupOptions = dimensionsInGroup.map(dim => {
return {
value: dim.name,
label:
labelize(dim.name.split('.').slice(-1)[0]) +
(dim.properties?.includes('primary_key') ? ' (PK)' : ''),
};
});
return (
<details key={grouping[0]}>
<summary style={{ marginBottom: '10px' }}>{groupHeader}</summary>
<div className="dimensionsList">
{dimensionGroupOptions.map(dimension => {
return (
<div key={dimension.value}>
{dimension.label.split('[').slice(0)[0]} ⇢{' '}
<code className="DimensionAttribute">
{dimension.value}
</code>
</div>
);
})}
</div>
</details>
);
})}
</div>
);
}
export function NodeList({ nodes }) {
return nodes && nodes.length > 0 ? (
<ul className="backfills">
{nodes?.map(node => {
// GraphQL returns uppercase types (e.g., "SOURCE"), CSS classes expect lowercase
const nodeType = node.type?.toLowerCase() || '';
return (
<li
className="backfill"
style={{ marginBottom: '5px' }}
key={node.name}
>
<span
className={`node_type__${nodeType} badge node_type`}
style={{ marginRight: '5px' }}
role="dialog"
aria-hidden="false"
aria-label="NodeType"
>
{nodeType}
</span>
<a href={`/nodes/${node.name}`}>{node.name}</a>
</li>
);
})}
</ul>
) : (
<span style={{ display: 'block' }}>None</span>
);
}