Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion libs/filer/workspace_files_extensions_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,13 @@ func (w *WorkspaceFilesExtensionsClient) getNotebookStatByNameWithoutExt(ctx con
ext = notebook.ExtensionJupyter
}

// Extension-less notebook names cannot round-trip through [Stat] or [Read],
// which report them as not found, so return nil to have [ReadDir] omit the entry.
if ext == "" {
log.Warnf(ctx, "skipping notebook %s: no file extension is associated with notebook language %q", path.Join(w.root, name), stat.Language)
return nil, nil
}

// Modify the stat object path to include the extension. This stat object will be used
// to return the fs.DirEntry object in the ReadDir method.
stat.Path = stat.Path + ext
Expand Down Expand Up @@ -205,6 +212,7 @@ func (w *WorkspaceFilesExtensionsClient) ReadDir(ctx context.Context, name strin
}

seenPaths := make(map[string]workspace.ObjectInfo)
result := make([]fs.DirEntry, 0, len(entries))
for i := range entries {
info, err := entries[i].Info()
if err != nil {
Expand All @@ -218,6 +226,10 @@ func (w *WorkspaceFilesExtensionsClient) ReadDir(ctx context.Context, name strin
if err != nil {
return nil, err
}
// A nil stat means the notebook's language has no extension mapping; omit the entry.
if stat == nil {
continue
}
// Replace the entry with the new entry that includes the extension.
entries[i] = wsfsDirEntry{wsfsFileInfo{ObjectInfo: stat.ObjectInfo}}
}
Expand All @@ -232,9 +244,10 @@ func (w *WorkspaceFilesExtensionsClient) ReadDir(ctx context.Context, name strin
}
}
seenPaths[entries[i].Name()] = sysInfo
result = append(result, entries[i])
}

return entries, nil
return result, nil
}

// Note: The import API returns opaque internal errors for namespace clashes
Expand Down
86 changes: 86 additions & 0 deletions libs/filer/workspace_files_extensions_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,89 @@ func TestFilerWorkspaceFilesExtensionsErrorsOnDupName(t *testing.T) {
})
}
}

func TestFilerWorkspaceFilesExtensionsReadDirWithUnmappedLanguage(t *testing.T) {
for _, tc := range []struct {
name string
language workspace.Language
notebookExportFormat workspace.ExportFormat
expectedNames []string
}{
{
name: "source notebook with empty language is skipped",
language: "",
notebookExportFormat: workspace.ExportFormatSource,
expectedNames: []string{"bar.py"},
},
{
name: "source notebook with unknown language is skipped",
language: workspace.Language("FUTURELANG"),
notebookExportFormat: workspace.ExportFormatSource,
expectedNames: []string{"bar.py"},
},
{
name: "jupyter notebook with unknown language keeps the .ipynb extension",
language: workspace.Language("FUTURELANG"),
notebookExportFormat: workspace.ExportFormatJupyter,
expectedNames: []string{"bar.py", "foo.ipynb"},
},
} {
t.Run(tc.name, func(t *testing.T) {
mockedWorkspaceClient := mocks.NewMockWorkspaceClient(t)
mockedApiClient := mockApiClient{}

workspaceApi := mockedWorkspaceClient.GetMockWorkspaceAPI()
workspaceApi.EXPECT().ListAll(mock.Anything, workspace.ListWorkspaceRequest{
Path: "/dir",
}).Return([]workspace.ObjectInfo{
{
Path: "/dir/bar.py",
ObjectType: workspace.ObjectTypeFile,
},
{
Path: "/dir/foo",
Language: tc.language,
ObjectType: workspace.ObjectTypeNotebook,
},
}, nil)

// Mock the get-status call used to figure out the notebook's file extension.
statNotebook := wsfsFileInfo{
ObjectInfo: workspace.ObjectInfo{
Path: "/dir/foo",
Language: tc.language,
ObjectType: workspace.ObjectTypeNotebook,
},
ReposExportFormat: tc.notebookExportFormat,
}

mockedApiClient.On("Do", mock.Anything, http.MethodGet, "/api/2.0/workspace/get-status", map[string]string(nil), map[string]string{
"path": "/dir/foo",
"return_export_info": "true",
}, mock.AnythingOfType("*filer.wsfsFileInfo"), []func(*http.Request) error(nil)).Return(nil, statNotebook)

workspaceFilesClient := WorkspaceFilesClient{
workspaceClient: mockedWorkspaceClient.WorkspaceClient,
apiClient: &mockedApiClient,
root: NewWorkspaceRootPath("/dir"),
}

workspaceFilesExtensionsClient := WorkspaceFilesExtensionsClient{
workspaceClient: mockedWorkspaceClient.WorkspaceClient,
wsfs: &workspaceFilesClient,
}

entries, err := workspaceFilesExtensionsClient.ReadDir(t.Context(), "/")
assert.NoError(t, err)

names := make([]string, len(entries))
for i, entry := range entries {
names[i] = entry.Name()
}
assert.Equal(t, tc.expectedNames, names)

workspaceApi.AssertNumberOfCalls(t, "ListAll", 1)
mockedApiClient.AssertNumberOfCalls(t, "Do", 1)
})
}
}
Loading