Skip to content

Commit 7cb5685

Browse files
committed
feat: add support for catalog dynamic catalog filtering
Signed-off-by: Sidney Glinton <[email protected]>
1 parent 909d494 commit 7cb5685

File tree

6 files changed

+125
-2
lines changed

6 files changed

+125
-2
lines changed

catalog/internal/catalog/catalog.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,8 @@ type APIProvider interface {
4040
// model is found with that name, it returns nil. If the model is
4141
// found, but has no artifacts, an empty list is returned.
4242
GetArtifacts(ctx context.Context, modelName string, sourceID string, params ListArtifactsParams) (model.CatalogArtifactList, error)
43+
44+
// GetFilterOptions returns all available filter options for models.
45+
// This includes field names, data types, and available values or ranges.
46+
GetFilterOptions(ctx context.Context) (*model.FilterOptionsList, error)
4347
}

catalog/internal/catalog/db_catalog.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,61 @@ func (d *dbCatalogImpl) GetArtifacts(ctx context.Context, modelName string, sour
167167
return *artifactList, nil
168168
}
169169

170+
func (d *dbCatalogImpl) GetFilterOptions(ctx context.Context) (*apimodels.FilterOptionsList, error) {
171+
// Max length threshold for filter values (excludes verbose fields like readme, description)
172+
const maxFilterValueLength = 100
173+
174+
// Query database for filterable properties
175+
filterableProps, err := d.catalogModelRepository.GetFilterableProperties(maxFilterValueLength)
176+
if err != nil {
177+
return nil, err
178+
}
179+
180+
// Build FilterOptionsList
181+
options := make(map[string]apimodels.FilterOption)
182+
183+
// Process each property and its values
184+
for fieldName, values := range filterableProps {
185+
// Skip internal/technical fields that shouldn't be exposed as filters
186+
if fieldName == "source_id" || fieldName == "logo" || fieldName == "license_link" {
187+
continue
188+
}
189+
190+
// Deduplicate values
191+
uniqueValues := make(map[string]bool)
192+
193+
// Parse JSON arrays for fields like language and tasks
194+
for _, value := range values {
195+
var arrayValues []string
196+
if err := json.Unmarshal([]byte(value), &arrayValues); err == nil {
197+
// Successfully parsed as array, add individual values
198+
for _, v := range arrayValues {
199+
uniqueValues[v] = true
200+
}
201+
} else {
202+
// Not a JSON array
203+
uniqueValues[value] = true
204+
}
205+
}
206+
207+
if len(uniqueValues) > 0 {
208+
expandedValues := make([]interface{}, 0, len(uniqueValues))
209+
for v := range uniqueValues {
210+
expandedValues = append(expandedValues, v)
211+
}
212+
213+
options[fieldName] = apimodels.FilterOption{
214+
Type: "string",
215+
Values: expandedValues,
216+
}
217+
}
218+
}
219+
220+
return &apimodels.FilterOptionsList{
221+
Filters: &options,
222+
}, nil
223+
}
224+
170225
func mapDBModelToAPIModel(m dbmodels.CatalogModel) apimodels.CatalogModel {
171226
res := apimodels.CatalogModel{}
172227

catalog/internal/catalog/hf_catalog.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ func (h *hfCatalogImpl) GetArtifacts(ctx context.Context, modelName string, sour
4949
}, nil
5050
}
5151

52+
func (h *hfCatalogImpl) GetFilterOptions(ctx context.Context) (*openapi.FilterOptionsList, error) {
53+
// TODO: Implement HuggingFace filter options retrieval
54+
// For now, return empty options to satisfy interface
55+
emptyFilters := make(map[string]openapi.FilterOption)
56+
return &openapi.FilterOptionsList{
57+
Filters: &emptyFilters,
58+
}, nil
59+
}
60+
5261
// validateCredentials checks if the HuggingFace API credentials are valid
5362
func (h *hfCatalogImpl) validateCredentials(ctx context.Context) error {
5463
glog.Infof("Validating HuggingFace API credentials")

catalog/internal/db/models/catalog_model.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,7 @@ type CatalogModelRepository interface {
3737
GetByName(name string) (CatalogModel, error)
3838
List(listOptions CatalogModelListOptions) (*models.ListWrapper[CatalogModel], error)
3939
Save(model CatalogModel) (CatalogModel, error)
40+
// GetFilterableProperties returns a map of property names to their unique values
41+
// Only includes string properties where all values are shorter than maxLength
42+
GetFilterableProperties(maxLength int) (map[string][]string, error)
4043
}

catalog/internal/db/service/catalog_model.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,53 @@ func mapDataLayerToCatalogModel(modelCtx schema.Context, propertiesCtx []schema.
231231

232232
return catalogModel
233233
}
234+
235+
// GetFilterableProperties returns property names and their unique values
236+
// Only includes properties where ALL values are shorter than maxLength
237+
func (r *CatalogModelRepositoryImpl) GetFilterableProperties(maxLength int) (map[string][]string, error) {
238+
config := r.GetConfig()
239+
240+
// Simplified query: get distinct property name/value pairs
241+
query := `
242+
SELECT DISTINCT cp.name, cp.string_value
243+
FROM "ContextProperty" cp
244+
WHERE cp.context_id IN (
245+
SELECT id FROM "Context" WHERE type_id = ?
246+
)
247+
AND cp.name IN (
248+
-- Only include property names where max length is within threshold
249+
SELECT name FROM (
250+
SELECT name, MAX(CHAR_LENGTH(string_value)) as max_len
251+
FROM "ContextProperty"
252+
WHERE context_id IN (
253+
SELECT id FROM "Context" WHERE type_id = ?
254+
)
255+
AND string_value IS NOT NULL
256+
AND string_value != ''
257+
GROUP BY name
258+
) AS field_lengths
259+
WHERE max_len <= ?
260+
)
261+
AND cp.string_value IS NOT NULL
262+
AND cp.string_value != ''
263+
ORDER BY cp.name, cp.string_value
264+
`
265+
266+
type propertyRow struct {
267+
Name string
268+
StringValue string
269+
}
270+
271+
var rows []propertyRow
272+
if err := config.DB.Raw(query, config.TypeID, config.TypeID, maxLength).Scan(&rows).Error; err != nil {
273+
return nil, fmt.Errorf("error querying filterable properties: %w", err)
274+
}
275+
276+
// Aggregate values by property name in Go
277+
result := make(map[string][]string)
278+
for _, row := range rows {
279+
result[row.Name] = append(result[row.Name], row.StringValue)
280+
}
281+
282+
return result, nil
283+
}

catalog/internal/server/openapi/type_asserts.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,10 @@ func AssertFilterOptionRequired(obj model.FilterOption) error {
283283
}
284284
}
285285

286-
if err := AssertFilterOptionRangeRequired(obj.Range); err != nil {
287-
return err
286+
if obj.Range != nil {
287+
if err := AssertFilterOptionRangeRequired(*obj.Range); err != nil {
288+
return err
289+
}
288290
}
289291
return nil
290292
}

0 commit comments

Comments
 (0)