Skip to content

Commit 545d44e

Browse files
authored
feat(data-modeling): show warning for invalid relationships COMPASS-9847 (#7520)
* show warning icon for invalid relationships * icon color * bootstrap * tests * bootstrap * bootstrap * copilot review * better name * bootstrap
1 parent 6a893cb commit 545d44e

File tree

9 files changed

+232
-440
lines changed

9 files changed

+232
-440
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,44 @@
99
NOTE: use `feat`, `fix` and `perf` for user facing changes that will be part of
1010
release notes.
1111
-->
12+
1213
## Description
14+
1315
<!--- Describe your changes in detail -->
1416
<!--- If applicable, describe (or illustrate) architecture flow -->
1517
<!--- If the UI changes in a non-trivial way, consider adding screenshots/video illustrating the new flows -->
1618

1719
### Checklist
20+
1821
- [ ] New tests and/or benchmarks are included
1922
- [ ] Documentation is changed or added
2023
- [ ] If this change updates the UI, screenshots/videos are added and a design review is requested
2124
- [ ] If this change could impact the load on the MongoDB cluster, please describe the expected and worst case impact
2225
- [ ] I have signed the MongoDB Contributor License Agreement (https://www.mongodb.com/legal/contributor-agreement)
2326

2427
## Motivation and Context
28+
2529
<!--- Why is this change required? What problem does it solve? -->
2630
<!--- If it's updating a dependency, link to the Pull Request that originally introduced the fix -->
31+
2732
- [ ] Bugfix
2833
- [ ] New feature
2934
- [ ] Dependency update
3035
- [ ] Misc
3136

3237
## Open Questions
38+
3339
<!--- Any particular areas you'd like reviewers to pay attention to? -->
3440

3541
## Dependents
42+
3643
<!--- If applicable, link PRs/commits that this PR is dependent on or is a dependency of. -->
3744

3845
## Types of changes
46+
3947
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
40-
- [ ] *Backport Needed*
48+
49+
- [ ] _Backport Needed_
4150
- [ ] Patch (non-breaking change which fixes an issue)
4251
- [ ] Minor (non-breaking change which adds functionality)
4352
- [ ] Major (fix or feature that would cause existing functionality to change)

package-lock.json

Lines changed: 85 additions & 422 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/compass-components/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@
9898
"@lg-chat/suggestions": "^0.2.3",
9999
"@lg-chat/title-bar": "^4.0.7",
100100
"@mongodb-js/compass-context-menu": "^0.3.0",
101-
"@mongodb-js/diagramming": "^2.2.0",
101+
"@mongodb-js/diagramming": "^2.2.1",
102102
"@react-aria/interactions": "^3.9.1",
103103
"@react-aria/utils": "^3.13.1",
104104
"@react-aria/visually-hidden": "^3.3.1",

packages/compass-data-modeling/src/components/diagram-editor.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
relationshipToDiagramEdge,
6161
} from '../utils/nodes-and-edges';
6262
import toNS from 'mongodb-ns';
63+
import { getNamespaceRelationships } from '../utils/utils';
6364
import { usePreference } from 'compass-preferences-model/provider';
6465

6566
const loadingContainerStyles = css({
@@ -236,6 +237,10 @@ const DiagramContent: React.FunctionComponent<{
236237
!!selectedItems &&
237238
selectedItems.type === 'collection' &&
238239
selectedItems.id === coll.ns;
240+
const relationships = getNamespaceRelationships(
241+
coll.ns,
242+
model?.relationships
243+
);
239244
return collectionToDiagramNode({
240245
...coll,
241246
highlightedFields,
@@ -245,6 +250,7 @@ const DiagramContent: React.FunctionComponent<{
245250
: undefined,
246251
selected,
247252
isInRelationshipDrawingMode,
253+
relationships,
248254
isExpanded: coll.isExpanded,
249255
});
250256
});

packages/compass-data-modeling/src/components/drawer/collection-drawer-content.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
} from './drawer-section-components';
2222
import { useChangeOnBlur } from './use-change-on-blur';
2323
import { RelationshipsSection } from './relationships-section';
24+
import { getNamespaceRelationships } from '../../utils/utils';
2425

2526
type CollectionDrawerContentProps = {
2627
namespace: string;
@@ -170,12 +171,10 @@ export default connect(
170171
namespace: collection.ns,
171172
isDraftCollection: state.diagram?.draftCollection === ownProps.namespace,
172173
collections: model.collections,
173-
relationships: model.relationships.filter((r) => {
174-
const [local, foreign] = r.relationship;
175-
return (
176-
local.ns === ownProps.namespace || foreign.ns === ownProps.namespace
177-
);
178-
}),
174+
relationships: getNamespaceRelationships(
175+
ownProps.namespace,
176+
model.relationships
177+
),
179178
};
180179
},
181180
{

packages/compass-data-modeling/src/components/drawer/relationships-section.tsx

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,17 @@ import {
44
Badge,
55
Button,
66
css,
7+
cx,
78
Icon,
89
IconButton,
910
palette,
1011
spacing,
12+
Tooltip,
13+
useDarkMode,
1114
} from '@mongodb-js/compass-components';
1215
import type { Relationship } from '../../services/data-model-storage';
1316
import { getDefaultRelationshipName } from '../../utils';
17+
import { isRelationshipValid } from '../../utils/utils';
1418

1519
const titleBtnStyles = css({
1620
marginLeft: 'auto',
@@ -35,13 +39,28 @@ const relationshipItemStyles = css({
3539
alignItems: 'center',
3640
});
3741

38-
const relationshipNameStyles = css({
42+
const relationshipNameWrapperStyles = css({
3943
flexGrow: 1,
44+
minWidth: 0,
45+
display: 'flex',
46+
alignItems: 'center',
47+
gap: spacing[100],
48+
paddingRight: spacing[200],
49+
});
50+
51+
const relationshipNameStyles = css({
4052
overflow: 'hidden',
4153
textOverflow: 'ellipsis',
4254
whiteSpace: 'nowrap',
43-
minWidth: 0,
44-
paddingRight: spacing[200],
55+
});
56+
57+
const warnIconWrapperStyles = css({
58+
display: 'flex',
59+
color: palette.yellow.dark2,
60+
});
61+
62+
const warnIconWrapperDarkStyles = css({
63+
color: palette.yellow.light2,
4564
});
4665

4766
const relationshipContentStyles = css({
@@ -65,6 +84,7 @@ export const RelationshipsSection: React.FunctionComponent<
6584
onEditRelationshipClick,
6685
onDeleteRelationshipClick,
6786
}) => {
87+
const darkmode = useDarkMode();
6888
return (
6989
<DMDrawerSection
7090
label={
@@ -97,12 +117,31 @@ export const RelationshipsSection: React.FunctionComponent<
97117
data-relationship-id={r.id}
98118
className={relationshipItemStyles}
99119
>
100-
<span
101-
className={relationshipNameStyles}
102-
title={relationshipLabel}
103-
>
104-
{relationshipLabel}
105-
</span>
120+
<div className={relationshipNameWrapperStyles}>
121+
<span
122+
className={relationshipNameStyles}
123+
title={relationshipLabel}
124+
>
125+
{relationshipLabel}
126+
</span>
127+
{!isRelationshipValid(r) && (
128+
<Tooltip
129+
trigger={
130+
<div
131+
className={cx(
132+
warnIconWrapperStyles,
133+
darkmode && warnIconWrapperDarkStyles
134+
)}
135+
>
136+
<Icon glyph="Warning" />
137+
</div>
138+
}
139+
>
140+
Cannot resolve the relationship - please verify the
141+
linked fields and namespace.
142+
</Tooltip>
143+
)}
144+
</div>
106145
<IconButton
107146
aria-label="Edit relationship"
108147
title="Edit relationship"

packages/compass-data-modeling/src/utils/nodes-and-edges.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type {
1313
Relationship,
1414
} from '../services/data-model-storage';
1515
import { traverseSchema } from './schema-traversal';
16-
import { areFieldPathsEqual, isIdField } from './utils';
16+
import { areFieldPathsEqual, isIdField, isRelationshipValid } from './utils';
1717

1818
const NO_HIGHLIGHTED_FIELDS = {};
1919

@@ -156,6 +156,7 @@ type CollectionWithRenderOptions = Pick<
156156
selectedField?: FieldPath;
157157
selected: boolean;
158158
isInRelationshipDrawingMode: boolean;
159+
relationships: Relationship[];
159160
};
160161

161162
export function collectionToDiagramNode({
@@ -166,8 +167,16 @@ export function collectionToDiagramNode({
166167
highlightedFields,
167168
selected,
168169
isInRelationshipDrawingMode,
170+
relationships,
169171
isExpanded,
170172
}: CollectionWithRenderOptions): NodeProps {
173+
let variant: NodeProps['variant'] = undefined;
174+
if (relationships.some((r) => !isRelationshipValid(r))) {
175+
variant = {
176+
type: 'warn' as const,
177+
warnMessage: 'One or more relationships cannot be resolved.',
178+
};
179+
}
171180
return {
172181
id: ns,
173182
type: 'collection',
@@ -185,6 +194,7 @@ export function collectionToDiagramNode({
185194
selected,
186195
connectable: isInRelationshipDrawingMode,
187196
draggable: !isInRelationshipDrawingMode,
197+
variant,
188198
};
189199
}
190200

packages/compass-data-modeling/src/utils/utils.spec.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
isRelationshipOfAField,
55
isSameFieldOrAncestor,
66
dualSourceHandlerDebounce,
7+
isRelationshipValid,
78
} from './utils';
89
import type { Relationship } from '../services/data-model-storage';
910

@@ -135,3 +136,42 @@ describe('dualSourceHandlerDebounce', function () {
135136
expect(invocationCount).to.equal(3);
136137
});
137138
});
139+
140+
describe('isRelationshipValid', function () {
141+
it('should return false for relationships with missing namespaces', function () {
142+
const relationship = {
143+
relationship: [
144+
{ ns: '', fields: ['a'], cardinality: 1 },
145+
{ ns: 'db.coll2', fields: ['b'], cardinality: 1 },
146+
],
147+
} as any;
148+
expect(isRelationshipValid(relationship)).to.be.false;
149+
});
150+
it('should return false for relationships with no fields', function () {
151+
const relationship = {
152+
relationship: [
153+
{ ns: 'db.coll1', fields: undefined, cardinality: 1 },
154+
{ ns: 'db.coll2', cardinality: 1 },
155+
],
156+
} as any;
157+
expect(isRelationshipValid(relationship)).to.be.false;
158+
});
159+
it('should return false for relationships with empty fields', function () {
160+
const relationship = {
161+
relationship: [
162+
{ ns: 'db.coll1', fields: [], cardinality: 1 },
163+
{ ns: 'db.coll2', fields: [], cardinality: 1 },
164+
],
165+
} as any;
166+
expect(isRelationshipValid(relationship)).to.be.false;
167+
});
168+
it('should return true for relationships with correct namespaces and fields', function () {
169+
const relationship = {
170+
relationship: [
171+
{ ns: 'db.coll1', fields: ['_id'], cardinality: 1 },
172+
{ ns: 'db.coll2', fields: ['order_id'], cardinality: 1 },
173+
],
174+
} as any;
175+
expect(isRelationshipValid(relationship)).to.be.true;
176+
});
177+
});

packages/compass-data-modeling/src/utils/utils.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,29 @@ export function dualSourceHandlerDebounce(
8484
};
8585
return Array.from({ length: count }, (_, i) => makeHandler(i));
8686
}
87+
88+
export function getNamespaceRelationships(
89+
namespace: string,
90+
relationships: Relationship[] = []
91+
): Relationship[] {
92+
return relationships.filter((r) => {
93+
const [local, foreign] = r.relationship;
94+
return local.ns === namespace || foreign.ns === namespace;
95+
});
96+
}
97+
98+
export function isRelationshipValid(relationship: Relationship): boolean {
99+
const [source, target] = relationship.relationship;
100+
if (
101+
!source.ns ||
102+
!target.ns ||
103+
!source.fields ||
104+
!target.fields ||
105+
source.fields.length === 0 ||
106+
target.fields.length === 0
107+
) {
108+
return false;
109+
}
110+
111+
return true;
112+
}

0 commit comments

Comments
 (0)