Skip to content

Commit e9e14a4

Browse files
authored
feat(data-modeling): add remove collection to diagram drawer COMPASS-9658 (#7186)
1 parent 7eddcf8 commit e9e14a4

File tree

7 files changed

+155
-23
lines changed

7 files changed

+155
-23
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,8 @@ export function getIsCollectionNameValid(
9393

9494
const isDuplicate = namespacesWithoutCurrent.some(
9595
(ns) =>
96-
ns === `${toNS(namespace).database}.${collectionName}` ||
97-
ns === `${toNS(namespace).database}.${collectionName.trim()}`
96+
ns.trim() ===
97+
`${toNS(namespace).database}.${collectionName.trim()}`.trim()
9898
);
9999

100100
return {
@@ -151,6 +151,7 @@ const CollectionDrawerContent: React.FunctionComponent<
151151
<DMFormFieldContainer>
152152
<TextInput
153153
label="Name"
154+
data-testid="data-model-collection-drawer-name-input"
154155
sizeVariant="small"
155156
value={collectionName}
156157
{...nameInputProps}

packages/compass-data-modeling/src/components/drawer/diagram-editor-side-panel.spec.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,30 @@ describe('DiagramEditorSidePanel', function () {
272272
});
273273
});
274274

275+
it('should delete a collection', async function () {
276+
const result = renderDrawer();
277+
result.plugin.store.dispatch(selectCollection('flights.countries'));
278+
279+
await waitFor(() => {
280+
expect(screen.getByLabelText('Name')).to.have.value('countries');
281+
});
282+
283+
userEvent.click(screen.getByLabelText(/delete collection/i));
284+
285+
await waitFor(() => {
286+
expect(screen.queryByText('countries')).not.to.exist;
287+
});
288+
expect(screen.queryByLabelText('Name')).to.not.exist;
289+
290+
const modifiedCollection = selectCurrentModelFromState(
291+
result.plugin.store.getState()
292+
).collections.find((coll) => {
293+
return coll.ns === 'flights.countries';
294+
});
295+
296+
expect(modifiedCollection).to.be.undefined;
297+
});
298+
275299
it('should open and edit a collection name', async function () {
276300
const result = renderDrawer();
277301
result.plugin.store.dispatch(selectCollection('flights.countries'));

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

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import CollectionDrawerContent from './collection-drawer-content';
1010
import RelationshipDrawerContent from './relationship-drawer-content';
1111
import {
12+
deleteCollection,
1213
deleteRelationship,
1314
selectCurrentModelFromState,
1415
} from '../../store/diagram';
@@ -36,11 +37,13 @@ type DiagramEditorSidePanelProps = {
3637
type: 'relationship' | 'collection';
3738
label: string;
3839
} | null;
40+
onDeleteCollection: (ns: string) => void;
3941
onDeleteRelationship: (rId: string) => void;
4042
};
4143

4244
function DiagramEditorSidePanel({
4345
selectedItems,
46+
onDeleteCollection,
4447
onDeleteRelationship,
4548
}: DiagramEditorSidePanelProps) {
4649
const { content, label, actions, handleAction } = useMemo(() => {
@@ -53,8 +56,18 @@ function DiagramEditorSidePanel({
5356
namespace={selectedItems.id}
5457
></CollectionDrawerContent>
5558
),
56-
actions: [],
57-
handleAction: () => {},
59+
actions: [
60+
{
61+
action: 'delete',
62+
label: 'Delete Collection',
63+
icon: 'Trash' as const,
64+
},
65+
],
66+
handleAction: (actionName: string) => {
67+
if (actionName === 'delete') {
68+
onDeleteCollection(selectedItems.id);
69+
}
70+
},
5871
};
5972
}
6073

@@ -79,7 +92,7 @@ function DiagramEditorSidePanel({
7992
}
8093

8194
return { content: null };
82-
}, [selectedItems, onDeleteRelationship]);
95+
}, [selectedItems, onDeleteCollection, onDeleteRelationship]);
8396

8497
if (!content) {
8598
return null;
@@ -97,6 +110,7 @@ function DiagramEditorSidePanel({
97110
<ItemActionControls
98111
actions={actions}
99112
iconSize="small"
113+
data-testid="data-modeling-drawer-actions"
100114
onAction={handleAction}
101115
className={drawerTitleActionGroupStyles}
102116
// Because the close button here is out of our control, we have do
@@ -127,7 +141,21 @@ export default connect(
127141
};
128142
}
129143

144+
const model = selectCurrentModelFromState(state);
145+
130146
if (selected.type === 'collection') {
147+
const doesCollectionExist = model.collections.find((collection) => {
148+
return collection.ns === selected.id;
149+
});
150+
151+
if (!doesCollectionExist) {
152+
// TODO(COMPASS-9680): When the selected collection doesn't exist then we
153+
// don't show any selection. We can get into this state with undo/redo.
154+
return {
155+
selectedItems: null,
156+
};
157+
}
158+
131159
return {
132160
selectedItems: {
133161
...selected,
@@ -137,15 +165,16 @@ export default connect(
137165
}
138166

139167
if (selected.type === 'relationship') {
140-
const model = selectCurrentModelFromState(state);
141168
const relationship = model.relationships.find((relationship) => {
142169
return relationship.id === selected.id;
143170
});
144171

145172
if (!relationship) {
146-
throw new Error(
147-
'Can not find corresponding relationship when rendering DiagramEditorSidePanel'
148-
);
173+
// TODO(COMPASS-9680): When the selected relationship doesn't exist we don't
174+
// show any selection. We can get into this state with undo/redo.
175+
return {
176+
selectedItems: null,
177+
};
149178
}
150179

151180
return {
@@ -157,6 +186,7 @@ export default connect(
157186
}
158187
},
159188
{
189+
onDeleteCollection: deleteCollection,
160190
onDeleteRelationship: deleteRelationship,
161191
}
162192
)(DiagramEditorSidePanel);

packages/compass-data-modeling/src/services/data-model-storage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ const EditSchemaVariants = z.discriminatedUnion('type', [
6666
ns: z.string(),
6767
newPosition: z.tuple([z.number(), z.number()]),
6868
}),
69+
z.object({
70+
type: z.literal('RemoveCollection'),
71+
ns: z.string(),
72+
}),
6973
z.object({
7074
type: z.literal('RenameCollection'),
7175
fromNS: z.string(),

packages/compass-data-modeling/src/store/diagram.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -269,15 +269,6 @@ const updateSelectedItemsFromAppliedEdit = (
269269
}
270270

271271
switch (edit.type) {
272-
case 'RemoveRelationship': {
273-
if (
274-
currentSelection?.type === 'relationship' &&
275-
currentSelection.id === edit.relationshipId
276-
) {
277-
return null;
278-
}
279-
break;
280-
}
281272
case 'RenameCollection': {
282273
if (
283274
currentSelection?.type === 'collection' &&
@@ -534,6 +525,12 @@ export function deleteRelationship(
534525
};
535526
}
536527

528+
export function deleteCollection(
529+
ns: string
530+
): DataModelingThunkAction<boolean, ApplyEditAction | ApplyEditFailedAction> {
531+
return applyEdit({ type: 'RemoveCollection', ns });
532+
}
533+
537534
export function updateCollectionNote(
538535
ns: string,
539536
note: string
@@ -591,6 +588,20 @@ function _applyEdit(edit: Edit, model?: StaticModel): StaticModel {
591588
}),
592589
};
593590
}
591+
case 'RemoveCollection': {
592+
return {
593+
...model,
594+
// Remove any relationships involving the collection being removed.
595+
relationships: model.relationships.filter((r) => {
596+
return !(
597+
r.relationship[0].ns === edit.ns || r.relationship[1].ns === edit.ns
598+
);
599+
}),
600+
collections: model.collections.filter(
601+
(collection) => collection.ns !== edit.ns
602+
),
603+
};
604+
}
594605
case 'RenameCollection': {
595606
return {
596607
...model,

packages/compass-e2e-tests/helpers/selectors.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,9 @@ export const DataModelsListItemActions = (diagramName: string) =>
14791479
`${DataModelsListItem(diagramName)} [aria-label="Show actions"]`;
14801480
export const DataModelsListItemDeleteButton = `[data-action="delete"]`;
14811481
export const DataModelAddRelationshipBtn = 'aria/Add relationship';
1482-
export const DataModelNameInput = '//label[text()="Name"]';
1482+
export const DataModelNameInputLabel = '//label[text()="Name"]';
1483+
export const DataModelNameInput =
1484+
'input[data-testid="data-model-collection-drawer-name-input"]';
14831485
export const DataModelRelationshipLocalCollectionSelect =
14841486
'//label[text()="Local collection"]';
14851487
export const DataModelRelationshipLocalFieldSelect =
@@ -1498,6 +1500,8 @@ export const DataModelCollectionRelationshipItem = (relationshipId: string) =>
14981500
`li[data-relationship-id="${relationshipId}"]`;
14991501
export const DataModelCollectionRelationshipItemEdit = `[aria-label="Edit relationship"]`;
15001502
export const DataModelCollectionRelationshipItemDelete = `[aria-label="Delete relationship"]`;
1503+
export const DataModelCollectionSidebarItemDelete = `[aria-label="Delete collection"]`;
1504+
export const DataModelCollectionSidebarItemDeleteButton = `[data-action="delete"]`;
15011505

15021506
// Side drawer
15031507
export const SideDrawer = `[data-testid="${getDrawerIds().root}"]`;

packages/compass-e2e-tests/tests/data-modeling-tab.test.ts

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,10 @@ async function selectCollectionOnTheDiagram(
9595
// If the drawer is open, close it
9696
// Otherwise the drawer or the minimap can cover the collection node
9797
const drawer = browser.$(Selectors.SideDrawer);
98-
if (await drawer.isDisplayed()) {
98+
if (
99+
(await drawer.isDisplayed()) &&
100+
(await drawer.$(Selectors.SideDrawerCloseButton).isClickable())
101+
) {
99102
await browser.clickVisible(Selectors.SideDrawerCloseButton);
100103
await drawer.waitForDisplayed({ reverse: true });
101104
}
@@ -109,7 +112,7 @@ async function selectCollectionOnTheDiagram(
109112
await drawer.waitForDisplayed();
110113

111114
const collectionName = await browser.getInputByLabel(
112-
drawer.$(Selectors.DataModelNameInput)
115+
browser.$(Selectors.SideDrawer).$(Selectors.DataModelNameInputLabel)
113116
);
114117
expect(await collectionName.getValue()).to.equal(toNS(ns).collection);
115118
}
@@ -439,8 +442,6 @@ describe('Data Modeling tab', function () {
439442

440443
const text = data.text.toLowerCase();
441444

442-
console.log(`Recognized PNG export text:`, text);
443-
444445
expect(text).to.include('testCollection-one'.toLowerCase());
445446
expect(text).to.include('testCollection-two'.toLowerCase());
446447

@@ -675,5 +676,62 @@ describe('Data Modeling tab', function () {
675676
'testCollection-two'
676677
);
677678
});
679+
680+
it('allows collection management via the sidebar', async function () {
681+
const dataModelName = 'Test Edit Collection';
682+
await setupDiagram(browser, {
683+
diagramName: dataModelName,
684+
connectionName: DEFAULT_CONNECTION_NAME_1,
685+
databaseName: 'test',
686+
});
687+
688+
const dataModelEditor = browser.$(Selectors.DataModelEditor);
689+
await dataModelEditor.waitForDisplayed();
690+
691+
// Click on the collection to open the drawer.
692+
await selectCollectionOnTheDiagram(browser, 'test.testCollection-one');
693+
694+
const drawer = browser.$(Selectors.SideDrawer);
695+
696+
// Rename the collection (it submits on unfocus).
697+
await browser.setValueVisible(
698+
browser.$(Selectors.DataModelNameInput),
699+
'testCollection-renamedOne'
700+
);
701+
await drawer.click(); // Unfocus the input.
702+
703+
// Verify that the renamed collection is still selected.
704+
await browser.waitUntil(async () => {
705+
const collectionName = await browser.getInputByLabel(
706+
browser.$(Selectors.SideDrawer).$(Selectors.DataModelNameInputLabel)
707+
);
708+
return (
709+
(await collectionName.getValue()) === 'testCollection-renamedOne'
710+
);
711+
});
712+
713+
// Select the second collection and verify that the new name is in the diagram.
714+
await selectCollectionOnTheDiagram(browser, 'test.testCollection-two');
715+
const nodes = await getDiagramNodes(browser, 2);
716+
expect(nodes).to.have.lengthOf(2);
717+
expect(nodes[0].id).to.equal('test.testCollection-renamedOne');
718+
expect(nodes[1].id).to.equal('test.testCollection-two');
719+
720+
// Remove the collection.
721+
await drawer
722+
.$(Selectors.DataModelCollectionSidebarItemDeleteButton)
723+
.click();
724+
// Ensure the drawer closed.
725+
if (await drawer.$(Selectors.DataModelNameInputLabel).isDisplayed()) {
726+
await drawer
727+
.$(Selectors.DataModelNameInputLabel)
728+
.waitForDisplayed({ reverse: true });
729+
}
730+
731+
// Verify that the collection is removed from the list and the diagram.
732+
const nodesPostDelete = await getDiagramNodes(browser, 1);
733+
expect(nodesPostDelete).to.have.lengthOf(1);
734+
expect(nodesPostDelete[0].id).to.equal('test.testCollection-renamedOne');
735+
});
678736
});
679737
});

0 commit comments

Comments
 (0)