Repository: jupyterlab-contrib/jupyterlab-favorites (language: python)

Issue:
Favourites cannot be defined for servers with user-specific server root

## Description

Currently there is no way to pre-define favourites for users on jupyterhub if they get different server roots. For example if one user gets `serverRoot = "/home/user_a"` and another gets `serverRoot = "/home/user_b"` we cannot predefine the favourites as these would be filtered out for one or the other user by this logic:

https://github.com/jupyterlab-contrib/jupyterlab-favorites/blob/74e63f22333be70ce25663166b4eedf93d9d089d/src/manager.ts#L47-L61

## Expected behavior

Either:
- (a) there is a setting allowing to disable filtering by `serverRoot` (when active all paths are taken as relative to current server root)
- (b) there is some wildcard/regex support in `serverRoot` resolution, so we can specify `serverRoot: "/home/*" and the filter will pass

Any preferences?

## Context

- Python package version: 3.2.1


Discussion / hints:

Fix patch:
diff --git a/schema/favorites.json b/schema/favorites.json
index 3d79214..f8ee11a 100644
--- a/schema/favorites.json
+++ b/schema/favorites.json
@@ -22,10 +22,11 @@
           "type": "string"
         },
         "root": {
-          "type": "string"
+          "type": "string",
+          "description": "Root path used for filtering out favourites from instances of JupyterLab launched in a different root directory. If not given the favourite will be shown if the associated file exists."
         }
       },
-      "required": ["root", "path"],
+      "required": ["path"],
       "type": "object"
     }
   },
diff --git a/src/manager.ts b/src/manager.ts
index 987950c..f8d3ecf 100644
--- a/src/manager.ts
+++ b/src/manager.ts
@@ -4,7 +4,7 @@ import { ISettingRegistry } from '@jupyterlab/settingregistry';
 import { CommandRegistry } from '@lumino/commands';
 import { Contents } from '@jupyterlab/services';
 import { IFavorites, SettingIDs, CommandIDs } from './token';
-import { getName } from './utils';
+import { getName, Optional } from './utils';
 
 export class FavoritesManager {
   favoritesMenu: Menu;
@@ -188,11 +188,17 @@ export class FavoritesManager {
   }
 
   private async loadFavorites() {
-    const favorites = await this._settingsRegistry.get(
+    const setting = await this._settingsRegistry.get(
       SettingIDs.favorites,
       'favorites'
     );
-    this.favorites = (favorites.composite ?? []) as IFavorites.Favorite[];
+    const favorites = (setting.composite ?? []) as Optional<
+      IFavorites.Favorite,
+      'root'
+    >[];
+    this.favorites = favorites.map(favorite => {
+      return { ...favorite, root: favorite.root ?? this.serverRoot };
+    });
   }
 
   private async loadShowWidget() {
diff --git a/src/utils.ts b/src/utils.ts
index 303df06..fe32e87 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -22,3 +22,5 @@ export function getPinnerActionDescription(showRemove: boolean): string {
 export function mergePaths(root: string, path: string): string {
   return PathExt.join(root, path);
 }
+
+export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;


Test patch:
diff --git a/ui-tests/tests/jupyterlab_favorites.spec.ts b/ui-tests/tests/jupyterlab_favorites.spec.ts
index 8e343f0..ea69a51 100644
--- a/ui-tests/tests/jupyterlab_favorites.spec.ts
+++ b/ui-tests/tests/jupyterlab_favorites.spec.ts
@@ -7,7 +7,7 @@ test.beforeEach(async ({ page }) => {
     .getByRole('tab', { name: 'untitled.txt' })
     .waitFor();
   await page.activity.closeAll();
-  await page.getByRole('button', { name: 'New Folder' }).click();
+  await page.getByTitle('New Folder').click();
   await page.locator('.jp-DirListing-editor').press('Escape');
 });