Skip to content

Commit 15015ec

Browse files
authored
Merge pull request #145 from RRosio/fs_err_ui
Display inactive filesystem error on hover
2 parents cc0749b + e6a6493 commit 15015ec

5 files changed

Lines changed: 92 additions & 8 deletions

File tree

docs/_static/s3fs_inactive.png

31.1 KB
Loading

docs/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,13 @@ the server's working directory in both the browser UI and in the kernel side.
123123
Since the kernel is usually a fully privileged process, this restriction only applies to the automatic behavior of jupyter_fsspec.
124124
:::
125125

126+
### Inactive Filesystems
127+
128+
Filesystems that are not instantiated due to an error will appear grayed out and will display an error message on hover.
129+
On click, there will be more information logged to the browser console.
130+
131+
![Jupyter FSSpec Inactive Filesystem](_static/s3fs_inactive.png 'Jupyter FSSpec Inactive Filesystem')
132+
126133
## The `helper` module
127134

128135
You can import the `jupyter_fsspec.helper` module into your notebooks to interact with

jupyter_fsspec/handlers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,10 +463,10 @@ async def get(self):
463463

464464
fs_instance = fs["instance"]
465465
response = {}
466-
is_async = fs_instance.async_impl
467466

468467
try:
469468
with handle_exception(self):
469+
is_async = fs_instance.async_impl
470470
result = (
471471
await fs_instance._ls(item_path, detail=True)
472472
if is_async

src/FssFilesysItem.ts

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,24 @@ class FssFilesysItem {
3636

3737
const fsItem = document.createElement('div');
3838
fsItem.classList.add('jfss-fsitem-root');
39+
this.root = fsItem;
3940

4041
if ('error' in fsInfo) {
4142
fsItem.classList.add('jfss-fsitem-error');
43+
fsItem.dataset.errorMessage = fsInfo.error.short_traceback;
44+
fsItem.addEventListener(
45+
'mouseenter',
46+
this.handleDisplayFSError.bind(this)
47+
);
48+
} else {
49+
fsItem.addEventListener('mouseenter', this.handleFsysHover.bind(this));
50+
fsItem.addEventListener('mouseleave', this.handleFsysHover.bind(this));
51+
52+
// Set the tooltip
53+
this.root.title = `Root Path: ${fsInfo.path}`;
4254
}
43-
fsItem.addEventListener('mouseenter', this.handleFsysHover.bind(this));
44-
fsItem.addEventListener('mouseleave', this.handleFsysHover.bind(this));
45-
fsItem.dataset.fssname = fsInfo.name;
46-
this.root = fsItem;
4755

48-
// Set the tooltip
49-
this.root.title = `Root Path: ${fsInfo.path}`;
56+
fsItem.dataset.fssname = fsInfo.name;
5057

5158
this.nameField = document.createElement('div');
5259
this.nameField.classList.add('jfss-fsitem-name');
@@ -176,6 +183,48 @@ class FssFilesysItem {
176183
}
177184
}
178185

186+
handleDisplayFSError(event: MouseEvent): void {
187+
const fsItem = event.currentTarget as HTMLElement;
188+
const errorMessage = fsItem.dataset.errorMessage;
189+
190+
const tooltip = document.createElement('div');
191+
tooltip.className = 'jfss-fsitem-tooltip';
192+
tooltip.textContent = `[Inactive] ${errorMessage}`;
193+
194+
Object.assign(tooltip.style, {
195+
position: 'fixed',
196+
backgroundColor: 'rgba(242, 159, 159, 0.85)',
197+
color: 'rgb(77, 16, 16)',
198+
padding: '4px 8px',
199+
boxShadow: '0 2px 6px rgba(0,0,0,0.2)',
200+
zIndex: '9999',
201+
fontSize: '12px',
202+
visibility: 'hidden'
203+
});
204+
205+
document.body.appendChild(tooltip);
206+
207+
// Measure and position tooltip
208+
const offset = 10;
209+
const { clientX: x, clientY: y } = event;
210+
const { width, height } = tooltip.getBoundingClientRect();
211+
const maxX = window.innerWidth - width - offset;
212+
const maxY = window.innerHeight - height - offset;
213+
214+
const left = Math.min(x + offset, maxX);
215+
const top = Math.min(y + offset, maxY);
216+
217+
tooltip.style.left = `${left}px`;
218+
tooltip.style.top = `${top}px`;
219+
tooltip.style.visibility = 'visible';
220+
221+
const removeTooltip = () => {
222+
tooltip.remove();
223+
fsItem.removeEventListener('mouseleave', removeTooltip);
224+
};
225+
fsItem.addEventListener('mouseleave', removeTooltip);
226+
}
227+
179228
handleFsysHover(event: any) {
180229
if (event.type === 'mouseenter') {
181230
this.hovered = true;
@@ -190,10 +239,11 @@ class FssFilesysItem {
190239
protocol: this.filesysProtocol,
191240
path: this.fsInfo.path
192241
});
193-
if (this.fsInfo.error) {
242+
if ('error' in this.fsInfo) {
194243
this.logger.error('Inactive filesystem', {
195244
...this.fsInfo.error
196245
});
246+
return;
197247
}
198248
this.selected = true;
199249
this.filesysClicked.emit(this.fsInfo);

ui-tests/tests/filesystem_interaction.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,33 @@ test('test open jupyterFsspec with hdfs config', async ({ page }) => {
192192
.toHaveText('Path: myhdfs');
193193
});
194194

195+
test('test filesystem error on hover', async ({ page }) => {
196+
await page.route('http://localhost:8888/jupyter_fsspec/config?**', route => {
197+
route.fulfill({
198+
status: 200,
199+
contentType: 'application/json',
200+
body: JSON.stringify(hdfsConfig)
201+
});
202+
});
203+
204+
await page.goto();
205+
await page.getByText('FSSpec', { exact: true }).click();
206+
207+
// verify filesystem item was created
208+
await expect.soft(page.locator('.jfss-fsitem-root')).toBeVisible();
209+
210+
// filesystem details should match as expected
211+
await expect.soft(page.locator('.jfss-fsitem-error')).toBeVisible();
212+
await expect.soft(page.locator('.jfss-fsitem-name')).toBeVisible();
213+
page.locator('.jfss-fsitem-error').hover();
214+
await expect.soft(page.locator('.jfss-fsitem-tooltip')).toBeVisible();
215+
await expect
216+
.soft(page.locator('.jfss-fsitem-tooltip'))
217+
.toContainText('[Inactive]');
218+
page.mouse.move(0, 0);
219+
await expect.soft(page.locator('.jfss-fsitem-tooltip')).toBeHidden();
220+
});
221+
195222
test('test memory filesystem with mock config data', async ({ page }) => {
196223
await page.goto();
197224
await page.getByText('FSSpec', { exact: true }).click();

0 commit comments

Comments
 (0)