Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs

This commit is contained in:
Ryan McKinley
2025-07-08 06:29:39 -07:00
302 changed files with 10413 additions and 1836 deletions
+1 -1
View File
@@ -47,7 +47,7 @@ func (f *Frontend) BuildFile(ctx context.Context, builder *dagger.Container, opt
}
func (f *Frontend) BuildDir(ctx context.Context, builder *dagger.Container, opts *pipeline.ArtifactContainerOpts) (*dagger.Directory, error) {
return frontend.Build(builder), nil
return frontend.Build(builder, f.Version), nil
}
func (f *Frontend) Publisher(ctx context.Context, opts *pipeline.ArtifactContainerOpts) (*dagger.Container, error) {
@@ -164,7 +164,7 @@ func NewTarball(
if err != nil {
return nil, err
}
frontendArtifact, err := NewFrontend(ctx, log, version, artifact, enterprise, src, cache)
frontendArtifact, err := NewFrontend(ctx, log, artifact, version, enterprise, src, cache)
if err != nil {
return nil, err
}
+4 -1
View File
@@ -1,11 +1,14 @@
package frontend
import (
"fmt"
"dagger.io/dagger"
)
func Build(builder *dagger.Container) *dagger.Directory {
func Build(builder *dagger.Container, version string) *dagger.Directory {
public := builder.
WithExec([]string{"/bin/sh", "-c", fmt.Sprintf("yarn lerna version %s --exact --no-git-tag-version --no-push --force-publish -y", version)}).
WithExec([]string{"yarn", "run", "build"}).
WithExec([]string{"/bin/sh", "-c", "find /src/public -type d -name node_modules -print0 | xargs -0 rm -rf"}).
Directory("/src/public")
+51 -4
View File
@@ -426,6 +426,11 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
if folderFilter.parseError != nil {
return model.LibraryElementSearchResult{}, folderFilter.parseError
}
foldersWithMatchingTitles, err := getFoldersWithMatchingTitles(c, l, signedInUser, query)
if err != nil {
return model.LibraryElementSearchResult{}, err
}
err = l.SQLStore.WithDbSession(c, func(session *db.Session) error {
builder := db.NewSqlBuilder(l.Cfg, l.features, l.SQLStore.GetDialect(), recursiveQueriesAreSupported)
if folderFilter.includeGeneralFolder {
@@ -434,7 +439,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()))
builder.Write(` WHERE le.org_id=? AND le.folder_id=0`, signedInUser.GetOrgID())
writeKindSQL(query, &builder)
writeSearchStringSQL(query, l.SQLStore, &builder)
writeSearchStringSQL(query, l.SQLStore, &builder, foldersWithMatchingTitles)
writeExcludeSQL(query, &builder)
writeTypeFilterSQL(typeFilter, &builder)
builder.Write(" ")
@@ -446,7 +451,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
builder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()))
builder.Write(` WHERE le.org_id=? AND le.folder_id<>0`, signedInUser.GetOrgID())
writeKindSQL(query, &builder)
writeSearchStringSQL(query, l.SQLStore, &builder)
writeSearchStringSQL(query, l.SQLStore, &builder, foldersWithMatchingTitles)
writeExcludeSQL(query, &builder)
writeTypeFilterSQL(typeFilter, &builder)
if err := folderFilter.writeFolderFilterSQL(false, &builder); err != nil {
@@ -525,7 +530,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
countBuilder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()))
countBuilder.Write(` WHERE le.org_id=? AND le.folder_id=0`, signedInUser.GetOrgID())
writeKindSQL(query, &countBuilder)
writeSearchStringSQL(query, l.SQLStore, &countBuilder)
writeSearchStringSQL(query, l.SQLStore, &countBuilder, foldersWithMatchingTitles)
writeExcludeSQL(query, &countBuilder)
writeTypeFilterSQL(typeFilter, &countBuilder)
countBuilder.Write(" ")
@@ -536,7 +541,7 @@ func (l *LibraryElementService) getAllLibraryElements(c context.Context, signedI
countBuilder.Write(getFromLibraryElementDTOWithMeta(l.SQLStore.GetDialect()))
countBuilder.Write(` WHERE le.org_id=? AND le.folder_id<>0`, signedInUser.GetOrgID())
writeKindSQL(query, &countBuilder)
writeSearchStringSQL(query, l.SQLStore, &countBuilder)
writeSearchStringSQL(query, l.SQLStore, &countBuilder, foldersWithMatchingTitles)
writeExcludeSQL(query, &countBuilder)
writeTypeFilterSQL(typeFilter, &countBuilder)
if err := folderFilter.writeFolderFilterSQL(true, &countBuilder); err != nil {
@@ -882,3 +887,45 @@ func contains(slice []string, element string) bool {
}
return false
}
func getFoldersWithMatchingTitles(c context.Context, l *LibraryElementService, signedInUser identity.Requester, query model.SearchLibraryElementsQuery) ([]string, error) {
if len(strings.TrimSpace(query.SearchString)) <= 0 {
return nil, nil
}
if l.features.IsEnabled(c, featuremgmt.FlagKubernetesClientDashboardsFolders) {
searchQuery := folder.SearchFoldersQuery{
OrgID: signedInUser.GetOrgID(),
Title: query.SearchString,
SignedInUser: signedInUser,
}
folderHits, err := l.folderService.SearchFolders(c, searchQuery)
if err != nil {
return nil, err
}
foldersWithMatchingTitles := make([]string, 0, len(folderHits))
for _, hit := range folderHits {
foldersWithMatchingTitles = append(foldersWithMatchingTitles, hit.UID)
}
return foldersWithMatchingTitles, nil
}
// Fallback to GetFolders
fs, err := l.folderService.GetFolders(c, folder.GetFoldersQuery{
OrgID: signedInUser.GetOrgID(),
SignedInUser: signedInUser,
})
if err != nil {
return nil, err
}
foldersWithMatchingTitles := make([]string, 0, len(fs))
for _, f := range fs {
if strings.Contains(strings.ToLower(f.Title), strings.ToLower(query.SearchString)) {
foldersWithMatchingTitles = append(foldersWithMatchingTitles, f.UID)
}
}
return foldersWithMatchingTitles, nil
}
@@ -1164,4 +1164,183 @@ func TestIntegration_GetAllLibraryElements(t *testing.T) {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
})
// Folder name search integration tests
scenarioWithPanel(t, "When searching by folder name, it should return panels in that folder",
func(t *testing.T, sc scenarioContext) {
// Create a panel in the existing folder
// nolint:staticcheck
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Panel in ScenarioFolder")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
// Search by folder name
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("searchString", "ScenarioFolder")
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryElementsSearch
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
// Verify folder search finds both panels
require.Equal(t, int64(2), result.Result.TotalCount)
require.Equal(t, 2, len(result.Result.Elements))
require.Equal(t, 1, result.Result.Page)
require.Equal(t, 100, result.Result.PerPage)
for _, element := range result.Result.Elements {
require.Equal(t, int64(1), element.OrgID, "Should be in org 1")
require.Equal(t, sc.folder.UID, element.FolderUID, "Should have correct folder UID")
require.Equal(t, "ScenarioFolder", element.Meta.FolderName, "All panels should be in ScenarioFolder")
require.Equal(t, int64(model.PanelElement), element.Kind, "Should be a panel element")
require.Equal(t, "text", element.Type, "Should be text panel")
require.NotEmpty(t, element.UID, "Should have a UID")
require.NotEmpty(t, element.Name, "Should have a name")
require.Equal(t, int64(0), element.Meta.ConnectedDashboards, "Should have no connected dashboards")
}
})
scenarioWithPanel(t, "When searching by folder name that doesn't exist, it should return no results",
func(t *testing.T, sc scenarioContext) {
// Search by non-existent folder name
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("searchString", "NonExistentFolder")
resp := sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryElementsSearch
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
// Verify no results for non-existent folder
expected := libraryElementsSearch{
Result: libraryElementsSearchResult{
TotalCount: 0,
Elements: []libraryElement{},
Page: 1,
PerPage: 100,
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
require.Equal(t, 0, len(result.Result.Elements), "Should return empty elements array")
})
scenarioWithPanel(t, "When searching with folder filter applied, folder name search should be disabled",
func(t *testing.T, sc scenarioContext) {
// Create a panel in the existing folder
// nolint:staticcheck
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Panel for folder filter test")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
// Search with both searchString and folderFilterUIDs
// This should NOT search folder names, only panel names/descriptions within the specified folder
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("searchString", "ScenarioFolder")
sc.reqContext.Req.Form.Add("folderFilterUIDs", sc.folder.UID)
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryElementsSearch
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
// Folder name search disabled when folder filter applied
expected := libraryElementsSearch{
Result: libraryElementsSearchResult{
TotalCount: 0,
Elements: []libraryElement{},
Page: 1,
PerPage: 100,
},
}
if diff := cmp.Diff(expected, result, getCompareOptions()...); diff != "" {
t.Fatalf("Result mismatch (-want +got):\n%s", diff)
}
require.Equal(t, int64(0), result.Result.TotalCount, "Should not find panels by folder name when folder filter is applied")
require.Equal(t, 0, len(result.Result.Elements), "Should return empty elements array")
})
scenarioWithPanel(t, "When searching by partial folder name, it should return panels in matching folders",
func(t *testing.T, sc scenarioContext) {
// Create a panel in the existing folder
// nolint:staticcheck
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Test Panel")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
// Search by partial folder name
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("searchString", "Scenario")
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryElementsSearch
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
// Verify partial folder name matching
require.Equal(t, int64(2), result.Result.TotalCount, "Should find exactly 2 panels by partial folder name")
require.Equal(t, 2, len(result.Result.Elements), "Should return 2 elements")
require.Equal(t, 1, result.Result.Page, "Should be on page 1")
require.Equal(t, 100, result.Result.PerPage, "Should have perPage 100")
for _, element := range result.Result.Elements {
require.Equal(t, int64(1), element.OrgID, "Should be in org 1")
require.Equal(t, sc.folder.UID, element.FolderUID, "Should have correct folder UID")
require.Contains(t, element.Meta.FolderName, "Scenario", "Folder name should contain 'Scenario'")
require.Equal(t, "ScenarioFolder", element.Meta.FolderName, "Should be in ScenarioFolder")
require.Equal(t, int64(model.PanelElement), element.Kind, "Should be a panel element")
require.NotEmpty(t, element.UID, "Should have a UID")
require.NotEmpty(t, element.Name, "Should have a name")
}
})
scenarioWithPanel(t, "When searching combines panel name and folder name matches, it should return both",
func(t *testing.T, sc scenarioContext) {
// Create a panel with a specific name
// nolint:staticcheck
command := getCreatePanelCommand(sc.folder.ID, sc.folder.UID, "Marketing Report Panel")
sc.reqContext.Req.Body = mockRequestBody(command)
resp := sc.service.createHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
// Search for "Marketing" which could match both panel name and folder name
err := sc.reqContext.Req.ParseForm()
require.NoError(t, err)
sc.reqContext.Req.Form.Add("searchString", "Marketing")
resp = sc.service.getAllHandler(sc.reqContext)
require.Equal(t, 200, resp.Status())
var result libraryElementsSearch
err = json.Unmarshal(resp.Body(), &result)
require.NoError(t, err)
// Verify panel name matching
require.Equal(t, int64(1), result.Result.TotalCount, "Should find exactly 1 panel with 'Marketing' in name")
require.Equal(t, 1, len(result.Result.Elements), "Should return 1 element")
require.Equal(t, 1, result.Result.Page, "Should be on page 1")
require.Equal(t, 100, result.Result.PerPage, "Should have perPage 100")
element := result.Result.Elements[0]
require.Equal(t, "Marketing Report Panel", element.Name, "Should find panel with correct name")
require.Equal(t, int64(1), element.OrgID, "Should be in org 1")
require.Equal(t, sc.folder.UID, element.FolderUID, "Should have correct folder UID")
require.Equal(t, "ScenarioFolder", element.Meta.FolderName, "Should be in ScenarioFolder")
require.Equal(t, int64(model.PanelElement), element.Kind, "Should be a panel element")
require.Equal(t, "text", element.Type, "Should be text panel")
require.NotEmpty(t, element.UID, "Should have a UID")
require.Equal(t, int64(0), element.Meta.ConnectedDashboards, "Should have no connected dashboards")
})
}
+17 -2
View File
@@ -58,13 +58,28 @@ func writeTypeFilterSQL(typeFilter []string, builder *db.SQLBuilder) {
}
}
func writeSearchStringSQL(query model.SearchLibraryElementsQuery, sqlStore db.DB, builder *db.SQLBuilder) {
func writeSearchStringSQL(query model.SearchLibraryElementsQuery, sqlStore db.DB, builder *db.SQLBuilder, foldersWithMatchingTitles []string) {
if len(strings.TrimSpace(query.SearchString)) > 0 {
// Search element names across all accessible folders
sql, param := sqlStore.GetDialect().LikeOperator("le.name", true, query.SearchString, true)
builder.Write(" AND ("+sql, param)
// Search element descriptions across all accessible folders
sql, param = sqlStore.GetDialect().LikeOperator("le.description", true, query.SearchString, true)
builder.Write(" OR "+sql+")", param)
builder.Write(" OR "+sql, param)
// Include ALL elements from folders whose titles match the search string
hasFolderFilter := len(strings.TrimSpace(query.FolderFilterUIDs)) > 0
if !hasFolderFilter && len(foldersWithMatchingTitles) > 0 {
folderUIDsSQL := "?" + strings.Repeat(",?", len(foldersWithMatchingTitles)-1)
params := make([]any, 0, len(foldersWithMatchingTitles))
for _, folderUID := range foldersWithMatchingTitles {
params = append(params, folderUID)
}
builder.Write(" OR le.folder_uid IN ("+folderUIDsSQL+")", params...)
}
builder.Write(")")
}
}
+6 -2
View File
@@ -36,7 +36,9 @@ func TestEncryptExtraConfigs(t *testing.T) {
m := fakes.NewFakeSecretsService()
c := &alertmanagerCrypto{
secrets: m,
ExtraConfigsCrypto: &ExtraConfigsCrypto{
secrets: m,
},
}
cfg := &definitions.PostableUserConfig{
@@ -83,7 +85,9 @@ func TestDecryptExtraConfigs(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
m := fakes.NewFakeSecretsService()
c := &alertmanagerCrypto{
secrets: m,
ExtraConfigsCrypto: &ExtraConfigsCrypto{
secrets: m,
},
}
cfg := &definitions.PostableUserConfig{
@@ -1276,7 +1276,7 @@
"default": ""
},
"legacyOptions": {
"description": "Catch-all field for datasource-specific properties",
"description": "Catch-all field for datasource-specific properties. Should not be available in as code tooling.",
"type": "object",
"additionalProperties": {
"type": "object"