Skip to content

Commit 26d3bd1

Browse files
authored
When dropping an unsupported file onto a notebook entry, tell the user it isnt supported (#7115)
* handle unknown files and deal with copy/paste * add some nice errors if couch pukes * added how to adjust couchdb limits * throw error on anntotation change * keep blockquotes * add test for blockquotes * allow multi-file drop of images too * add test for big files * spell check * check for null * need to ignore console errors * reorder tests so we can ignore console errors * when creating new entry, ready it for editing * fix tests and empty embeds * fix tests * found similar issue from notebooks in plots
1 parent a16a1d3 commit 26d3bd1

File tree

12 files changed

+260
-72
lines changed

12 files changed

+260
-72
lines changed

.cspell.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,11 @@
483483
"websockets",
484484
"swgs",
485485
"memlab",
486-
"devmode"
486+
"devmode",
487+
"blockquote",
488+
"blockquotes",
489+
"Blockquote",
490+
"Blockquotes"
487491
],
488492
"dictionaries": ["npm", "softwareTerms", "node", "html", "css", "bash", "en_US"],
489493
"ignorePaths": [

e2e/helper/notebookUtils.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ async function enterTextEntry(page, text) {
3434
await page.locator(NOTEBOOK_DROP_AREA).click();
3535

3636
// enter text
37-
await page.getByLabel('Notebook Entry Display').last().click();
3837
await page.getByLabel('Notebook Entry Input').last().fill(text);
3938
await commitEntry(page);
4039
}
@@ -53,6 +52,7 @@ async function dragAndDropEmbed(page, notebookObject) {
5352
await page.click('button[title="Show selected item in tree"]');
5453
// Drag and drop the SWG into the notebook
5554
await page.dragAndDrop(`text=${swg.name}`, NOTEBOOK_DROP_AREA);
55+
await commitEntry(page);
5656
}
5757

5858
/**

e2e/tests/functional/plugins/notebook/notebook.e2e.spec.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ test.describe('Notebook entry tests', () => {
279279

280280
// Click .c-notebook__drag-area
281281
await page.locator('.c-notebook__drag-area').click();
282-
await expect(page.getByLabel('Notebook Entry Display')).toBeVisible();
282+
await expect(page.getByLabel('Notebook Entry Input')).toBeVisible();
283283
await expect(page.getByLabel('Notebook Entry', { exact: true })).toHaveClass(/is-selected/);
284284
});
285285
test('When an object is dropped into a notebook, a new entry is created and it should be focused @unstable', async ({
@@ -514,10 +514,23 @@ test.describe('Notebook entry tests', () => {
514514
const childItem = page.locator('li:has-text("List Item 2") ol li:has-text("Order 2")');
515515
await expect(childItem).toBeVisible();
516516

517-
// Blocks
518-
const blockTest = '```javascript\nconst foo = "bar";\nconst bar = "foo";\n```';
519-
await nbUtils.enterTextEntry(page, blockTest);
517+
// Code Blocks
518+
const codeblockTest = '```javascript\nconst foo = "bar";\nconst bar = "foo";\n```';
519+
await nbUtils.enterTextEntry(page, codeblockTest);
520520
const codeBlock = page.locator('code.language-javascript:has-text("const foo = \\"bar\\";")');
521521
await expect(codeBlock).toBeVisible();
522+
523+
// Blockquotes
524+
const blockquoteTest =
525+
'This is a quote by Mark Twain:\n> "The man with a new idea is a crank\n>until the idea succeeds."';
526+
await nbUtils.enterTextEntry(page, blockquoteTest);
527+
const firstLineOfBlockquoteText = page.locator(
528+
'blockquote:has-text("The man with a new idea is a crank")'
529+
);
530+
await expect(firstLineOfBlockquoteText).toBeVisible();
531+
const secondLineOfBlockquoteText = page.locator(
532+
'blockquote:has-text("until the idea succeeds")'
533+
);
534+
await expect(secondLineOfBlockquoteText).toBeVisible();
522535
});
523536
});

e2e/tests/functional/plugins/notebook/notebookSnapshots.e2e.spec.js

+57-2
Original file line numberDiff line numberDiff line change
@@ -188,12 +188,11 @@ test.describe('Snapshot image tests', () => {
188188
}, fileData);
189189

190190
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
191-
191+
await page.locator('.c-ne__save-button > button').click();
192192
// be sure that entry was created
193193
await expect(page.getByText('favicon-96x96.png')).toBeVisible();
194194

195195
await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).click();
196-
197196
// expect large image to be displayed
198197
await expect(page.getByRole('dialog').getByText('favicon-96x96.png')).toBeVisible();
199198

@@ -215,3 +214,59 @@ test.describe('Snapshot image tests', () => {
215214
expect(await page.getByRole('img', { name: 'favicon-96x96.png thumbnail' }).count()).toBe(1);
216215
});
217216
});
217+
218+
test.describe('Snapshot image failure tests', () => {
219+
test.use({ failOnConsoleError: false });
220+
test.beforeEach(async ({ page }) => {
221+
//Navigate to baseURL
222+
await page.goto('./', { waitUntil: 'domcontentloaded' });
223+
224+
// Create Notebook
225+
await createDomainObjectWithDefaults(page, {
226+
type: NOTEBOOK_NAME
227+
});
228+
});
229+
230+
test('Get an error notification when dropping unknown file onto notebook entry', async ({
231+
page
232+
}) => {
233+
// fill Uint8Array array with some garbage data
234+
const garbageData = new Uint8Array(100);
235+
const fileData = Array.from(garbageData);
236+
237+
const dropTransfer = await page.evaluateHandle((data) => {
238+
const dataTransfer = new DataTransfer();
239+
const file = new File([new Uint8Array(data)], 'someGarbage.foo', { type: 'unknown/garbage' });
240+
dataTransfer.items.add(file);
241+
return dataTransfer;
242+
}, fileData);
243+
244+
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: dropTransfer });
245+
246+
// should have gotten a notification from OpenMCT that we couldn't add it
247+
await expect(page.getByText('Unknown object(s) dropped and cannot embed')).toBeVisible();
248+
});
249+
250+
test('Get an error notification when dropping big files onto notebook entry', async ({
251+
page
252+
}) => {
253+
const garbageSize = 15 * 1024 * 1024; // 15 megabytes
254+
255+
await page.addScriptTag({
256+
// make the garbage client side
257+
content: `window.bigGarbageData = new Uint8Array(${garbageSize})`
258+
});
259+
260+
const bigDropTransfer = await page.evaluateHandle(() => {
261+
const dataTransfer = new DataTransfer();
262+
const file = new File([window.bigGarbageData], 'bigBoy.png', { type: 'image/png' });
263+
dataTransfer.items.add(file);
264+
return dataTransfer;
265+
});
266+
267+
await page.dispatchEvent('.c-notebook__drag-area', 'drop', { dataTransfer: bigDropTransfer });
268+
269+
// should have gotten a notification from OpenMCT that we couldn't add it as it's too big
270+
await expect(page.getByText('unable to embed')).toBeVisible();
271+
});
272+
});

e2e/tests/functional/plugins/notebook/tags.e2e.spec.js

-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ test.describe('Tagging in Notebooks @addInit', () => {
150150
await createNotebookEntryAndTags(page);
151151

152152
await page.locator('text=To start a new entry, click here or drag and drop any object').click();
153-
await page.getByLabel('Notebook Entry Display').last().click();
154153
await page.getByLabel('Notebook Entry Input').fill(`An entry without tags`);
155154
await page.locator('.c-ne__save-button > button').click();
156155

e2e/tests/performance/contract/notebook.contract.perf.spec.js

-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ test.describe('Performance tests', () => {
131131
await page.evaluate(() => window.performance.mark('new-notebook-entry-created'));
132132

133133
// Enter Notebook Entry text
134-
await page.getByLabel('Notebook Entry').last().click();
135134
await page.getByLabel('Notebook Entry Input').last().fill('New Entry');
136135
await page.locator('.c-ne__save-button').click();
137136
await page.evaluate(() => window.performance.mark('new-notebook-entry-filled'));

src/plugins/notebook/components/NotebookComponent.vue

+40-15
Original file line numberDiff line numberDiff line change
@@ -625,29 +625,43 @@ export default {
625625
dropEvent.preventDefault();
626626
dropEvent.stopImmediatePropagation();
627627

628-
const localImageDropped = dropEvent.dataTransfer.files?.[0]?.type.includes('image');
629-
const imageUrl = dropEvent.dataTransfer.getData('URL');
628+
const dataTransferFiles = Array.from(dropEvent.dataTransfer.files);
629+
const localImageDropped = dataTransferFiles.some((file) => file.type.includes('image'));
630630
const snapshotId = dropEvent.dataTransfer.getData('openmct/snapshot/id');
631+
const domainObjectData = dropEvent.dataTransfer.getData('openmct/domain-object-path');
632+
const imageUrl = dropEvent.dataTransfer.getData('URL');
631633
if (localImageDropped) {
632-
// local image dropped from disk (file)
633-
const imageData = dropEvent.dataTransfer.files[0];
634-
const imageEmbed = await createNewImageEmbed(imageData, this.openmct, imageData?.name);
635-
this.newEntry(imageEmbed);
634+
// local image(s) dropped from disk (file)
635+
const embeds = [];
636+
await Promise.all(
637+
dataTransferFiles.map(async (file) => {
638+
if (file.type.includes('image')) {
639+
const imageData = file;
640+
const imageEmbed = await createNewImageEmbed(
641+
imageData,
642+
this.openmct,
643+
imageData?.name
644+
);
645+
embeds.push(imageEmbed);
646+
}
647+
})
648+
);
649+
this.newEntry(embeds);
636650
} else if (imageUrl) {
637651
// remote image dropped (URL)
638652
try {
639653
const response = await fetch(imageUrl);
640654
const imageData = await response.blob();
641655
const imageEmbed = await createNewImageEmbed(imageData, this.openmct);
642-
this.newEntry(imageEmbed);
656+
this.newEntry([imageEmbed]);
643657
} catch (error) {
644658
this.openmct.notifications.alert(`Unable to add image: ${error.message} `);
645659
console.error(`Problem embedding remote image`, error);
646660
}
647661
} else if (snapshotId.length) {
648662
// snapshot object
649663
const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
650-
this.newEntry(snapshot.embedObject);
664+
this.newEntry([snapshot.embedObject]);
651665
this.snapshotContainer.removeSnapshot(snapshotId);
652666

653667
const namespace = this.domainObject.identifier.namespace;
@@ -656,10 +670,9 @@ export default {
656670
namespace
657671
);
658672
saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
659-
} else {
673+
} else if (domainObjectData) {
660674
// plain domain object
661-
const data = dropEvent.dataTransfer.getData('openmct/domain-object-path');
662-
const objectPath = JSON.parse(data);
675+
const objectPath = JSON.parse(domainObjectData);
663676
const bounds = this.openmct.time.bounds();
664677
const snapshotMeta = {
665678
bounds,
@@ -668,8 +681,15 @@ export default {
668681
openmct: this.openmct
669682
};
670683
const embed = await createNewEmbed(snapshotMeta);
671-
672-
this.newEntry(embed);
684+
this.newEntry([embed]);
685+
} else {
686+
this.openmct.notifications.error(
687+
`Unknown object(s) dropped and cannot embed. Try again with an image or domain object.`
688+
);
689+
console.warn(
690+
`Unknown object(s) dropped and cannot embed. Try again with an image or domain object.`
691+
);
692+
return;
673693
}
674694
},
675695
focusOnEntryId() {
@@ -838,12 +858,12 @@ export default {
838858
getSelectedSectionId() {
839859
return this.selectedSection?.id;
840860
},
841-
async newEntry(embed, event) {
861+
async newEntry(embeds, event) {
842862
this.startTransaction();
843863
this.resetSearch();
844864
const notebookStorage = this.createNotebookStorageObject();
845865
this.updateDefaultNotebook(notebookStorage);
846-
const id = await addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embed);
866+
const id = await addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embeds);
847867

848868
const element = this.$refs.notebookEntries.querySelector(`#${id}`);
849869
const entryAnnotations = this.notebookAnnotations[id] ?? {};
@@ -861,6 +881,11 @@ export default {
861881
this.filterAndSortEntries();
862882
this.focusEntryId = id;
863883
this.selectedEntryId = id;
884+
885+
// put entry into edit mode
886+
this.$nextTick(() => {
887+
element.dispatchEvent(new Event('click'));
888+
});
864889
},
865890
orientationChange() {
866891
this.formatSidebar();

0 commit comments

Comments
 (0)