Storage: Add maxFiles to list functions (#76414)

* Storage: Add maxFiles to list functions

* Add maxDataPoints argument to listFiles function

* Add maxFiles to ResourceDimensionEditor

* Update pkg/services/store/http.go

* rename First to Limit

---------

Co-authored-by: jennyfana <110450222+jennyfana@users.noreply.github.com>
Co-authored-by: nmarrs <nathanielmarrs@gmail.com>
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
pull/77575/head
Drew Slobodnjak 2 years ago committed by GitHub
parent f6d3238505
commit 9116043453
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      pkg/infra/filestorage/api.go
  2. 2
      pkg/infra/filestorage/cdk_blob_filestorage.go
  3. 2
      pkg/infra/filestorage/db_filestorage.go
  4. 28
      pkg/infra/filestorage/fs_integration_test.go
  5. 2
      pkg/infra/filestorage/test_utils.go
  6. 8
      pkg/infra/filestorage/wrapper.go
  7. 3
      pkg/services/store/http.go
  8. 6
      pkg/services/store/service.go
  9. 20
      pkg/services/store/service_test.go
  10. 18
      pkg/services/store/tree.go
  11. 2
      pkg/services/store/types.go
  12. 3
      pkg/tsdb/grafanads/grafana.go
  13. 1
      public/app/features/canvas/elements/icon.tsx
  14. 7
      public/app/features/dimensions/editors/FolderPickerTab.tsx
  15. 2
      public/app/features/dimensions/editors/ResourceDimensionEditor.tsx
  16. 11
      public/app/features/dimensions/editors/ResourcePicker.tsx
  17. 4
      public/app/features/dimensions/editors/ResourcePickerPopover.tsx
  18. 1
      public/app/features/dimensions/types.ts
  19. 3
      public/app/plugins/datasource/grafana/datasource.ts
  20. 3
      public/app/plugins/panel/geomap/editor/StyleEditor.tsx

@ -93,8 +93,10 @@ type FileMetadata struct {
} }
type Paging struct { type Paging struct {
// The number of items to return
Limit int
// Starting after the key
After string After string
First int
} }
type UpsertFileCommand struct { type UpsertFileCommand struct {

@ -292,7 +292,7 @@ func (c cdkBlobStorage) list(ctx context.Context, folderPath string, paging *Pag
})} })}
recursive := options.Recursive recursive := options.Recursive
pageSize := paging.First pageSize := paging.Limit
foundCursor := true foundCursor := true
if paging.After != "" { if paging.After != "" {

@ -345,7 +345,7 @@ func (s dbFileStorage) List(ctx context.Context, folderPath string, paging *Pagi
sess.OrderBy("path") sess.OrderBy("path")
pageSize := paging.First pageSize := paging.Limit
sess.Limit(pageSize + 1) sess.Limit(pageSize + 1)
if cursor != "" { if cursor != "" {

@ -467,7 +467,7 @@ func TestIntegrationFsStorage(t *testing.T) {
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 2, After: ""}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{Limit: 2, After: ""}},
list: checks(listSize(2), listHasMore(true), listLastPath("/folder1/b")), list: checks(listSize(2), listHasMore(true), listLastPath("/folder1/b")),
files: [][]any{ files: [][]any{
checks(fPath("/folder1/a")), checks(fPath("/folder1/a")),
@ -475,7 +475,7 @@ func TestIntegrationFsStorage(t *testing.T) {
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 2, After: ""}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 2, After: ""}},
list: checks(listSize(2), listHasMore(true), listLastPath("/folder1/a")), list: checks(listSize(2), listHasMore(true), listLastPath("/folder1/a")),
files: [][]any{ files: [][]any{
checks(fPath("/folder1"), fMimeType(DirectoryMimeType)), checks(fPath("/folder1"), fMimeType(DirectoryMimeType)),
@ -483,49 +483,49 @@ func TestIntegrationFsStorage(t *testing.T) {
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 1, After: "/folder1"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 1, After: "/folder1"}},
list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/a")), list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/a")),
files: [][]any{ files: [][]any{
checks(fPath("/folder1/a")), checks(fPath("/folder1/a")),
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: "/folder1/a"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{Limit: 1, After: "/folder1/a"}},
list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/b")), list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/b")),
files: [][]any{ files: [][]any{
checks(fPath("/folder1/b")), checks(fPath("/folder1/b")),
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 1, After: "/folder1/a"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 1, After: "/folder1/a"}},
list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/b")), list: checks(listSize(1), listHasMore(true), listLastPath("/folder1/b")),
files: [][]any{ files: [][]any{
checks(fPath("/folder1/b")), checks(fPath("/folder1/b")),
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 1, After: "/folder1/b"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{Limit: 1, After: "/folder1/b"}},
list: checks(listSize(1), listHasMore(false), listLastPath("/folder2/c")), list: checks(listSize(1), listHasMore(false), listLastPath("/folder2/c")),
files: [][]any{ files: [][]any{
checks(fPath("/folder2/c")), checks(fPath("/folder2/c")),
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 1, After: "/folder1/b"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 1, After: "/folder1/b"}},
list: checks(listSize(1), listHasMore(true), listLastPath("/folder2")), list: checks(listSize(1), listHasMore(true), listLastPath("/folder2")),
files: [][]any{ files: [][]any{
checks(fPath("/folder2"), fMimeType(DirectoryMimeType)), checks(fPath("/folder2"), fMimeType(DirectoryMimeType)),
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 1, After: "/folder2"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 1, After: "/folder2"}},
list: checks(listSize(1), listHasMore(false), listLastPath("/folder2/c")), list: checks(listSize(1), listHasMore(false), listLastPath("/folder2/c")),
files: [][]any{ files: [][]any{
checks(fPath("/folder2/c")), checks(fPath("/folder2/c")),
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: ""}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{Limit: 5, After: ""}},
list: checks(listSize(3), listHasMore(false), listLastPath("/folder2/c")), list: checks(listSize(3), listHasMore(false), listLastPath("/folder2/c")),
files: [][]any{ files: [][]any{
checks(fPath("/folder1/a")), checks(fPath("/folder1/a")),
@ -534,7 +534,7 @@ func TestIntegrationFsStorage(t *testing.T) {
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 5, After: ""}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 5, After: ""}},
list: checks(listSize(5), listHasMore(false), listLastPath("/folder2/c")), list: checks(listSize(5), listHasMore(false), listLastPath("/folder2/c")),
files: [][]any{ files: [][]any{
checks(fPath("/folder1"), fMimeType(DirectoryMimeType)), checks(fPath("/folder1"), fMimeType(DirectoryMimeType)),
@ -545,19 +545,19 @@ func TestIntegrationFsStorage(t *testing.T) {
}, },
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: "/folder2"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{Limit: 5, After: "/folder2"}},
list: checks(listSize(1), listHasMore(false)), list: checks(listSize(1), listHasMore(false)),
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 5, After: "/folder2"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 5, After: "/folder2"}},
list: checks(listSize(1), listHasMore(false)), list: checks(listSize(1), listHasMore(false)),
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{First: 5, After: "/folder2/c"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true}, paging: &Paging{Limit: 5, After: "/folder2/c"}},
list: checks(listSize(0), listHasMore(false)), list: checks(listSize(0), listHasMore(false)),
}, },
queryListFiles{ queryListFiles{
input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{First: 5, After: "/folder2/c"}}, input: queryListFilesInput{path: "/", options: &ListOptions{Recursive: true, WithFiles: true, WithFolders: true}, paging: &Paging{Limit: 5, After: "/folder2/c"}},
list: checks(listSize(0), listHasMore(false)), list: checks(listSize(0), listHasMore(false)),
}, },
}, },

@ -321,7 +321,7 @@ func handleQuery(t *testing.T, ctx context.Context, query interface{}, queryName
} }
resp, err := fs.List(ctx, inputPath, &Paging{ resp, err := fs.List(ctx, inputPath, &Paging{
After: "", After: "",
First: 100000, Limit: 100000,
}, opts) }, opts)
require.NotNil(t, resp) require.NotNil(t, resp)
require.NoError(t, err, "%s: should be able to list folders in %s", queryName, inputPath) require.NoError(t, err, "%s: should be able to list folders in %s", queryName, inputPath)

@ -200,12 +200,12 @@ func (b wrapper) Upsert(ctx context.Context, file *UpsertFileCommand) error {
func (b wrapper) pagingOptionsWithDefaults(paging *Paging) *Paging { func (b wrapper) pagingOptionsWithDefaults(paging *Paging) *Paging {
if paging == nil { if paging == nil {
return &Paging{ return &Paging{
First: 100, Limit: 100,
} }
} }
if paging.First <= 0 { if paging.Limit <= 0 {
paging.First = 100 paging.Limit = 100
} }
if paging.After != "" { if paging.After != "" {
paging.After = b.addRoot(paging.After) paging.After = b.addRoot(paging.After)
@ -381,7 +381,7 @@ func (b wrapper) List(ctx context.Context, folderPath string, paging *Paging, op
} }
func (b wrapper) isFolderEmpty(ctx context.Context, path string) (bool, error) { func (b wrapper) isFolderEmpty(ctx context.Context, path string) (bool, error) {
resp, err := b.List(ctx, path, &Paging{First: 1}, &ListOptions{Recursive: true, WithFolders: true, WithFiles: true}) resp, err := b.List(ctx, path, &Paging{Limit: 1}, &ListOptions{Recursive: true, WithFolders: true, WithFiles: true})
if err != nil { if err != nil {
return false, err return false, err
} }

@ -260,7 +260,8 @@ func (s *standardStorageService) doCreateFolder(c *contextmodel.ReqContext) resp
func (s *standardStorageService) list(c *contextmodel.ReqContext) response.Response { func (s *standardStorageService) list(c *contextmodel.ReqContext) response.Response {
params := web.Params(c.Req) params := web.Params(c.Req)
path := params["*"] path := params["*"]
frame, err := s.List(c.Req.Context(), c.SignedInUser, path) // maxFiles of 0 will result in default behaviour from wrapper
frame, err := s.List(c.Req.Context(), c.SignedInUser, path, 0)
if err != nil { if err != nil {
return response.Error(400, "error reading path", err) return response.Error(400, "error reading path", err)
} }

@ -64,7 +64,7 @@ type StorageService interface {
RegisterHTTPRoutes(routing.RouteRegister) RegisterHTTPRoutes(routing.RouteRegister)
// List folder contents // List folder contents
List(ctx context.Context, user *user.SignedInUser, path string) (*StorageListFrame, error) List(ctx context.Context, user *user.SignedInUser, path string, maxFiles int) (*StorageListFrame, error)
// Read raw file contents out of the store // Read raw file contents out of the store
Read(ctx context.Context, user *user.SignedInUser, path string) (*filestorage.File, error) Read(ctx context.Context, user *user.SignedInUser, path string) (*filestorage.File, error)
@ -340,9 +340,9 @@ func getOrgId(user *user.SignedInUser) int64 {
return user.OrgID return user.OrgID
} }
func (s *standardStorageService) List(ctx context.Context, user *user.SignedInUser, path string) (*StorageListFrame, error) { func (s *standardStorageService) List(ctx context.Context, user *user.SignedInUser, path string, maxFiles int) (*StorageListFrame, error) {
guardian := s.authService.newGuardian(ctx, user, getFirstSegment(path)) guardian := s.authService.newGuardian(ctx, user, getFirstSegment(path))
return s.tree.ListFolder(ctx, getOrgId(user), path, guardian.getPathFilter(ActionFilesRead)) return s.tree.ListFolder(ctx, getOrgId(user), path, maxFiles, guardian.getPathFilter(ActionFilesRead))
} }
func (s *standardStorageService) Read(ctx context.Context, user *user.SignedInUser, path string) (*filestorage.File, error) { func (s *standardStorageService) Read(ctx context.Context, user *user.SignedInUser, path string) (*filestorage.File, error) {

@ -74,7 +74,7 @@ func TestListFiles(t *testing.T) {
store := newStandardStorageService(db.InitTestDB(t), roots, func(orgId int64) []storageRuntime { store := newStandardStorageService(db.InitTestDB(t), roots, func(orgId int64) []storageRuntime {
return make([]storageRuntime, 0) return make([]storageRuntime, 0)
}, allowAllAuthService, cfg, nil) }, allowAllAuthService, cfg, nil)
frame, err := store.List(context.Background(), dummyUser, "public/maps") frame, err := store.List(context.Background(), dummyUser, "public/maps", 0)
require.NoError(t, err) require.NoError(t, err)
experimental.CheckGoldenJSONFrame(t, "testdata", "public_testdata.golden", frame.Frame, true) experimental.CheckGoldenJSONFrame(t, "testdata", "public_testdata.golden", frame.Frame, true)
@ -95,7 +95,7 @@ func TestListFilesWithoutPermissions(t *testing.T) {
store := newStandardStorageService(db.InitTestDB(t), roots, func(orgId int64) []storageRuntime { store := newStandardStorageService(db.InitTestDB(t), roots, func(orgId int64) []storageRuntime {
return make([]storageRuntime, 0) return make([]storageRuntime, 0)
}, denyAllAuthService, cfg, nil) }, denyAllAuthService, cfg, nil)
frame, err := store.List(context.Background(), dummyUser, "public/maps") frame, err := store.List(context.Background(), dummyUser, "public/maps", 0)
require.NoError(t, err) require.NoError(t, err)
rowLen, err := frame.RowLen() rowLen, err := frame.RowLen()
require.NoError(t, err) require.NoError(t, err)
@ -371,7 +371,7 @@ func TestContentRootWithNestedStorage(t *testing.T) {
Files: []*filestorage.File{}, Files: []*filestorage.File{},
}, nil) }, nil)
_, err := store.List(context.Background(), test.user, RootContent+"/"+test.nestedRoot) _, err := store.List(context.Background(), test.user, RootContent+"/"+test.nestedRoot, 0)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -387,7 +387,7 @@ func TestContentRootWithNestedStorage(t *testing.T) {
Files: []*filestorage.File{}, Files: []*filestorage.File{},
}, nil) }, nil)
_, err := store.List(context.Background(), test.user, strings.Join([]string{RootContent, test.nestedRoot, "folder1", "folder2"}, "/")) _, err := store.List(context.Background(), test.user, strings.Join([]string{RootContent, test.nestedRoot, "folder1", "folder2"}, "/"), 0)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -434,16 +434,16 @@ func TestContentRootWithNestedStorage(t *testing.T) {
Files: []*filestorage.File{}, Files: []*filestorage.File{},
}, nil) }, nil)
_, err := store.List(context.Background(), test.user, strings.Join([]string{RootContent, "not-nested-content"}, "/")) _, err := store.List(context.Background(), test.user, strings.Join([]string{RootContent, "not-nested-content"}, "/"), 0)
require.NoError(t, err) require.NoError(t, err)
_, err = store.List(context.Background(), test.user, strings.Join([]string{RootContent, "a", "b", "c"}, "/")) _, err = store.List(context.Background(), test.user, strings.Join([]string{RootContent, "a", "b", "c"}, "/"), 0)
require.NoError(t, err) require.NoError(t, err)
_, err = store.List(context.Background(), test.user, strings.Join([]string{RootContent, test.nestedRoot + "a"}, "/")) _, err = store.List(context.Background(), test.user, strings.Join([]string{RootContent, test.nestedRoot + "a"}, "/"), 0)
require.NoError(t, err) require.NoError(t, err)
_, err = store.List(context.Background(), test.user, strings.Join([]string{RootContent, test.nestedRoot + "a", "b"}, "/")) _, err = store.List(context.Background(), test.user, strings.Join([]string{RootContent, test.nestedRoot + "a", "b"}, "/"), 0)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -536,7 +536,7 @@ func TestShadowingExistingFolderByNestedContentRoot(t *testing.T) {
AllowUnsanitizedSvgUpload: true, AllowUnsanitizedSvgUpload: true,
} }
resp, err := store.List(ctx, globalUser, "content/nested") resp, err := store.List(ctx, globalUser, "content/nested", 0)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, resp) require.NotNil(t, resp)
@ -544,7 +544,7 @@ func TestShadowingExistingFolderByNestedContentRoot(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 0, rowLen) // nested storage is empty require.Equal(t, 0, rowLen) // nested storage is empty
resp, err = store.List(ctx, globalUser, "content") resp, err = store.List(ctx, globalUser, "content", 0)
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, resp) require.NotNil(t, resp)

@ -165,7 +165,7 @@ func (t *nestedTree) getStorages(orgId int64) []storageRuntime {
return storages return storages
} }
func (t *nestedTree) ListFolder(ctx context.Context, orgId int64, path string, accessFilter filestorage.PathFilter) (*StorageListFrame, error) { func (t *nestedTree) ListFolder(ctx context.Context, orgId int64, path string, maxFiles int, accessFilter filestorage.PathFilter) (*StorageListFrame, error) {
if path == "" || path == "/" { if path == "" || path == "/" {
t.assureOrgIsInitialized(orgId) t.assureOrgIsInitialized(orgId)
@ -224,12 +224,16 @@ func (t *nestedTree) ListFolder(ctx context.Context, orgId int64, path string, a
) )
} }
listResponse, err := store.List(ctx, path, nil, &filestorage.ListOptions{ listResponse, err := store.List(ctx, path,
Recursive: false, &filestorage.Paging{
WithFolders: true, Limit: maxFiles,
WithFiles: true, },
Filter: pathFilter, &filestorage.ListOptions{
}) Recursive: false,
WithFolders: true,
WithFiles: true,
Filter: pathFilter,
})
if err != nil { if err != nil {
return nil, err return nil, err

@ -40,7 +40,7 @@ type WriteValueResponse struct {
type storageTree interface { type storageTree interface {
GetFile(ctx context.Context, orgId int64, path string) (*filestorage.File, error) GetFile(ctx context.Context, orgId int64, path string) (*filestorage.File, error)
ListFolder(ctx context.Context, orgId int64, path string, accessFilter filestorage.PathFilter) (*StorageListFrame, error) ListFolder(ctx context.Context, orgId int64, path string, maxFiles int, accessFilter filestorage.PathFilter) (*StorageListFrame, error)
} }
//------------------------------------------- //-------------------------------------------

@ -124,7 +124,8 @@ func (s *Service) doListQuery(ctx context.Context, query backend.DataQuery) back
} }
path := store.RootPublicStatic + "/" + q.Path path := store.RootPublicStatic + "/" + q.Path
listFrame, err := s.store.List(ctx, nil, path) maxFiles := int(query.MaxDataPoints)
listFrame, err := s.store.List(ctx, nil, path, maxFiles)
response.Error = err response.Error = err
if listFrame != nil { if listFrame != nil {
response.Frames = data.Frames{listFrame.Frame} response.Frames = data.Frames{listFrame.Frame}

@ -115,6 +115,7 @@ export const iconItem: CanvasElementItem<IconConfig, IconData> = {
editor: ResourceDimensionEditor, editor: ResourceDimensionEditor,
settings: { settings: {
resourceType: 'icon', resourceType: 'icon',
maxFiles: 2000,
}, },
}) })
.addCustomEditor({ .addCustomEditor({

@ -35,10 +35,11 @@ interface Props {
folderName: ResourceFolderName; folderName: ResourceFolderName;
newValue: string; newValue: string;
setNewValue: Dispatch<SetStateAction<string>>; setNewValue: Dispatch<SetStateAction<string>>;
maxFiles?: number;
} }
export const FolderPickerTab = (props: Props) => { export const FolderPickerTab = (props: Props) => {
const { value, mediaType, folderName, newValue, setNewValue } = props; const { value, mediaType, folderName, newValue, setNewValue, maxFiles } = props;
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const folders = getFolders(mediaType).map((v) => ({ const folders = getFolders(mediaType).map((v) => ({
@ -75,7 +76,7 @@ export const FolderPickerTab = (props: Props) => {
getDatasourceSrv() getDatasourceSrv()
.get('-- Grafana --') .get('-- Grafana --')
.then((ds) => { .then((ds) => {
(ds as GrafanaDatasource).listFiles(folder).subscribe({ (ds as GrafanaDatasource).listFiles(folder, maxFiles).subscribe({
next: (frame) => { next: (frame) => {
const cards: ResourceItem[] = []; const cards: ResourceItem[] = [];
frame.forEach((item) => { frame.forEach((item) => {
@ -95,7 +96,7 @@ export const FolderPickerTab = (props: Props) => {
}); });
}); });
} }
}, [mediaType, currentFolder]); }, [mediaType, currentFolder, maxFiles]);
return ( return (
<> <>

@ -65,6 +65,7 @@ export const ResourceDimensionEditor = (
const showSourceRadio = item.settings?.showSourceRadio ?? true; const showSourceRadio = item.settings?.showSourceRadio ?? true;
const mediaType = item.settings?.resourceType ?? MediaType.Icon; const mediaType = item.settings?.resourceType ?? MediaType.Icon;
const folderName = item.settings?.folderName ?? ResourceFolderName.Icon; const folderName = item.settings?.folderName ?? ResourceFolderName.Icon;
const maxFiles = item.settings?.maxFiles; // undefined leads to backend default
let srcPath = ''; let srcPath = '';
if (mediaType === MediaType.Icon) { if (mediaType === MediaType.Icon) {
if (value?.fixed) { if (value?.fixed) {
@ -106,6 +107,7 @@ export const ResourceDimensionEditor = (
mediaType={mediaType} mediaType={mediaType}
folderName={folderName} folderName={folderName}
size={ResourcePickerSize.NORMAL} size={ResourcePickerSize.NORMAL}
maxFiles={maxFiles}
/> />
)} )}
{mode === ResourceDimensionMode.Mapping && ( {mode === ResourceDimensionMode.Mapping && (

@ -32,17 +32,24 @@ interface Props {
name?: string; name?: string;
placeholder?: string; placeholder?: string;
color?: string; color?: string;
maxFiles?: number;
} }
export const ResourcePicker = (props: Props) => { export const ResourcePicker = (props: Props) => {
const { value, src, name, placeholder, onChange, onClear, mediaType, folderName, size, color } = props; const { value, src, name, placeholder, onChange, onClear, mediaType, folderName, size, color, maxFiles } = props;
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const theme = useTheme2(); const theme = useTheme2();
const pickerTriggerRef = createRef<HTMLDivElement>(); const pickerTriggerRef = createRef<HTMLDivElement>();
const popoverElement = ( const popoverElement = (
<ResourcePickerPopover onChange={onChange} value={value} mediaType={mediaType} folderName={folderName} /> <ResourcePickerPopover
onChange={onChange}
value={value}
mediaType={mediaType}
folderName={folderName}
maxFiles={maxFiles}
/>
); );
let sanitizedSrc = src; let sanitizedSrc = src;

@ -20,13 +20,14 @@ interface Props {
onChange: (value?: string) => void; onChange: (value?: string) => void;
mediaType: MediaType; mediaType: MediaType;
folderName: ResourceFolderName; folderName: ResourceFolderName;
maxFiles?: number;
} }
interface ErrorResponse { interface ErrorResponse {
message: string; message: string;
} }
export const ResourcePickerPopover = (props: Props) => { export const ResourcePickerPopover = (props: Props) => {
const { value, onChange, mediaType, folderName } = props; const { value, onChange, mediaType, folderName, maxFiles } = props;
const styles = useStyles2(getStyles); const styles = useStyles2(getStyles);
const onClose = () => { const onClose = () => {
@ -55,6 +56,7 @@ export const ResourcePickerPopover = (props: Props) => {
folderName={folderName} folderName={folderName}
newValue={newValue} newValue={newValue}
setNewValue={setNewValue} setNewValue={setNewValue}
maxFiles={maxFiles}
/> />
); );

@ -59,6 +59,7 @@ export interface ResourceDimensionOptions {
placeholderValue?: string; placeholderValue?: string;
// If you want your icon to be driven by value of a field // If you want your icon to be driven by value of a field
showSourceRadio?: boolean; showSourceRadio?: boolean;
maxFiles?: number;
} }
export enum ResourceFolderName { export enum ResourceFolderName {

@ -170,7 +170,7 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
return of(); // nothing return of(); // nothing
} }
listFiles(path: string): Observable<DataFrameView<FileElement>> { listFiles(path: string, maxDataPoints?: number): Observable<DataFrameView<FileElement>> {
return this.query({ return this.query({
targets: [ targets: [
{ {
@ -179,6 +179,7 @@ export class GrafanaDatasource extends DataSourceWithBackend<GrafanaQuery> {
path, path,
}, },
], ],
maxDataPoints,
} as any).pipe( } as any).pipe(
map((v) => { map((v) => {
const frame = v.data[0] ?? new MutableDataFrame(); const frame = v.data[0] ?? new MutableDataFrame();

@ -120,6 +120,7 @@ export const StyleEditor = (props: Props) => {
const propertyOptions = useObservable(settings?.layerInfo ?? of()); const propertyOptions = useObservable(settings?.layerInfo ?? of());
const featuresHavePoints = propertyOptions?.geometryType === GeometryTypeId.Point; const featuresHavePoints = propertyOptions?.geometryType === GeometryTypeId.Point;
const hasTextLabel = styleUsesText(value); const hasTextLabel = styleUsesText(value);
const maxFiles = 2000;
// Simple fixed value display // Simple fixed value display
if (settings?.simpleFixedValues) { if (settings?.simpleFixedValues) {
@ -141,6 +142,7 @@ export const StyleEditor = (props: Props) => {
placeholderText: hasTextLabel ? 'Select a symbol' : 'Select a symbol or add a text label', placeholderText: hasTextLabel ? 'Select a symbol' : 'Select a symbol or add a text label',
placeholderValue: defaultStyleConfig.symbol.fixed, placeholderValue: defaultStyleConfig.symbol.fixed,
showSourceRadio: false, showSourceRadio: false,
maxFiles,
}, },
} as StandardEditorsRegistryItem } as StandardEditorsRegistryItem
} }
@ -230,6 +232,7 @@ export const StyleEditor = (props: Props) => {
placeholderText: hasTextLabel ? 'Select a symbol' : 'Select a symbol or add a text label', placeholderText: hasTextLabel ? 'Select a symbol' : 'Select a symbol or add a text label',
placeholderValue: defaultStyleConfig.symbol.fixed, placeholderValue: defaultStyleConfig.symbol.fixed,
showSourceRadio: false, showSourceRadio: false,
maxFiles,
}, },
} as StandardEditorsRegistryItem } as StandardEditorsRegistryItem
} }

Loading…
Cancel
Save