Skip to content

Commit de010c4

Browse files
rsun19mturley
andauthored
Basic filters rendering (kubeflow#1698)
* inital implementation Signed-off-by: rsun19 <[email protected]> * added wrappers for basic filters Signed-off-by: rsun19 <[email protected]> * added filter mapping and fixed filters Signed-off-by: rsun19 <[email protected]> * added stronger types Signed-off-by: rsun19 <[email protected]> * changed useeffect Signed-off-by: rsun19 <[email protected]> * integrated bff stub Signed-off-by: rsun19 <[email protected]> * fixed filter search Signed-off-by: rsun19 <[email protected]> * fixed types Signed-off-by: rsun19 <[email protected]> * added tests Signed-off-by: rsun19 <[email protected]> * fixed lint warning Signed-off-by: rsun19 <[email protected]> * removed use effect Signed-off-by: rsun19 <[email protected]> * addressed some comments Signed-off-by: rsun19 <[email protected]> * fixed lint issue Signed-off-by: rsun19 <[email protected]> * fixed tests Signed-off-by: rsun19 <[email protected]> * Adjusting filter types Signed-off-by: Mike Turley <[email protected]> * Always show selected options even if they don't match search Signed-off-by: Mike Turley <[email protected]> * Fix Signed-off-by: Mike Turley <[email protected]> * fixed lint issue Signed-off-by: rsun19 <[email protected]> --------- Signed-off-by: rsun19 <[email protected]> Signed-off-by: Mike Turley <[email protected]> Co-authored-by: Mike Turley <[email protected]>
1 parent 90db6c0 commit de010c4

File tree

16 files changed

+867
-60
lines changed

16 files changed

+867
-60
lines changed

clients/ui/bff/internal/mocks/static_data_mock.go

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ func GetCatalogModelMocks() []models.CatalogModel {
317317
Name: "repo1/granite-8b-code-instruct",
318318
Description: stringToPointer("Granite-8B-Code-Instruct is a 8B parameter model fine tuned from\nGranite-8B-Code-Base on a combination of permissively licensed instruction\ndata to enhance instruction following capabilities including logical\nreasoning and problem-solving skills."),
319319
Provider: stringToPointer("provider1"),
320-
Tasks: []string{"text-generation", "task2", "task3", "task4"},
320+
Tasks: []string{"text-generation", "image-to-text"},
321321
License: stringToPointer("apache-2.0"),
322322
LicenseLink: stringToPointer("https://www.apache.org/licenses/LICENSE-2.0.txt"),
323323
Maturity: stringToPointer("Technology preview"),
@@ -670,8 +670,8 @@ Granite 3.1 Instruct Models are primarily finetuned using instruction-response p
670670
sampleModel2 := models.CatalogModel{
671671
Name: "repo1/granite-7b-instruct",
672672
Description: stringToPointer("Granite 7B instruction-tuned model for enterprise applications"),
673-
Provider: stringToPointer("provider1"),
674-
Tasks: []string{"text-generation", "instruction-following"},
673+
Provider: stringToPointer("Red Hat"),
674+
Tasks: []string{"text-generation", "image-text-to-text"},
675675
License: stringToPointer("apache-2.0"),
676676
Maturity: stringToPointer("Generally Available"),
677677
Language: []string{"en"},
@@ -683,9 +683,9 @@ Granite 3.1 Instruct Models are primarily finetuned using instruction-response p
683683
sampleModel3 := models.CatalogModel{
684684
Name: "repo1/granite-3b-code-base",
685685
Description: stringToPointer("Granite 3B code generation model for programming tasks"),
686-
Provider: stringToPointer("provider1"),
687-
Tasks: []string{"code-generation"},
688-
License: stringToPointer("apache-2.0"),
686+
Provider: stringToPointer("IBM"),
687+
Tasks: []string{"audio-to-text", "text-to-text", "video-to-text"},
688+
License: stringToPointer("mit"),
689689
Maturity: stringToPointer("Generally Available"),
690690
Language: []string{"en"},
691691
SourceId: stringToPointer("sample-source"),
@@ -695,8 +695,8 @@ Granite 3.1 Instruct Models are primarily finetuned using instruction-response p
695695
huggingFaceModel1 := models.CatalogModel{
696696
Name: "provider2/bert-base-uncased",
697697
Description: stringToPointer("BERT base model (uncased) - Pretrained model on English language"),
698-
Provider: stringToPointer("provider2"),
699-
Tasks: []string{"fill-mask", "feature-extraction"},
698+
Provider: stringToPointer("Google"),
699+
Tasks: []string{"audio-to-text", "text-to-text"},
700700
License: stringToPointer("apache-2.0"),
701701
Maturity: stringToPointer("Generally Available"),
702702
Language: []string{"en"},
@@ -708,7 +708,7 @@ Granite 3.1 Instruct Models are primarily finetuned using instruction-response p
708708
Name: "provider3/gpt2",
709709
Description: stringToPointer("GPT-2 is a transformers model pretrained on a very large corpus of English data"),
710710
Provider: stringToPointer("provider3"),
711-
Tasks: []string{"text-generation"},
711+
Tasks: []string{"video-to-text"},
712712
License: stringToPointer("mit"),
713713
Maturity: stringToPointer("Generally Available"),
714714
Language: []string{"en"},
@@ -801,12 +801,12 @@ func GetCatalogSourceMocks() []models.CatalogSource {
801801
Id: "sample-source",
802802
Name: "Sample mocked source",
803803
Enabled: &enabled,
804-
Labels: []string{"Sample category 1", "Sample categorey 2", "Community"},
804+
Labels: []string{"Sample category 1", "Sample category 2", "Community"},
805805
},
806806
{
807807
Id: "huggingface",
808808
Name: "Hugging Face",
809-
Labels: []string{"Sample categorey 2", "Community"},
809+
Labels: []string{"Sample category 2", "Community"},
810810
},
811811
{
812812
Id: "adminModel1",
@@ -818,7 +818,7 @@ func GetCatalogSourceMocks() []models.CatalogSource {
818818
Id: "adminModel2",
819819
Name: "Admin model 2",
820820
Enabled: &enabled,
821-
Labels: []string{"Sample categorey 1"},
821+
Labels: []string{"Sample category 1"},
822822
},
823823
{
824824
Id: "dora",
@@ -1158,12 +1158,7 @@ func GetFilterOptionMocks() map[string]models.FilterOption {
11581158
filterOptions["provider"] = models.FilterOption{
11591159
Type: FilterOptionTypeString,
11601160
Values: &[]interface{}{
1161-
"provider1",
1162-
"provider2",
1163-
"provider3",
1164-
"Hugging Face",
1165-
"Admin model 1",
1166-
"Admin model 2",
1161+
"Red Hat", "IBM", "Google",
11671162
},
11681163
}
11691164

@@ -1172,40 +1167,21 @@ func GetFilterOptionMocks() map[string]models.FilterOption {
11721167
Values: &[]interface{}{
11731168
"apache-2.0",
11741169
"mit",
1175-
"gpl-3.0",
1176-
"bsd-3-clause",
1177-
"cc-by-4.0",
1178-
"proprietary",
11791170
},
11801171
}
11811172

11821173
filterOptions["tasks"] = models.FilterOption{
11831174
Type: FilterOptionTypeString,
11841175
Values: &[]interface{}{
1185-
"text-generation",
1186-
"task2",
1187-
"task3",
1188-
"task4",
1189-
"text-classification",
1190-
"fill-mask",
1191-
"question-answering",
1192-
"summarization",
1193-
"translation",
1194-
"code-generation",
1195-
"instruction-following",
1196-
"feature-extraction",
1197-
"conversational",
1176+
"audio-to-text", "image-to-text", "image-text-to-text", "text-generation", "text-to-text", "video-to-text",
11981177
},
11991178
}
12001179

12011180
// String type filter for programming languages supported
12021181
filterOptions["language"] = models.FilterOption{
12031182
Type: FilterOptionTypeString,
12041183
Values: &[]interface{}{
1205-
"en",
1206-
"es",
1207-
"cz",
1208-
"zh",
1184+
"ar", "cs", "de", "en", "es", "fr", "it", "ja", "ko", "nl", "pt", "zh",
12091185
},
12101186
}
12111187

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { CatalogFilterOptionsList } from '~/app/modelCatalogTypes';
2+
import {
3+
ModelCatalogStringFilterKey,
4+
ModelCatalogLicense,
5+
ModelCatalogProvider,
6+
ModelCatalogTask,
7+
AllLanguageCode,
8+
} from '~/concepts/modelCatalog/const';
9+
10+
export const mockCatalogFilterOptionsList = (
11+
partial?: Partial<CatalogFilterOptionsList>,
12+
): CatalogFilterOptionsList => ({
13+
filters: {
14+
[ModelCatalogStringFilterKey.PROVIDER]: {
15+
type: 'string',
16+
values: [ModelCatalogProvider.RED_HAT, ModelCatalogProvider.IBM, ModelCatalogProvider.GOOGLE],
17+
},
18+
[ModelCatalogStringFilterKey.LICENSE]: {
19+
type: 'string',
20+
values: [ModelCatalogLicense.APACHE_2_0, ModelCatalogLicense.MIT],
21+
},
22+
[ModelCatalogStringFilterKey.TASK]: {
23+
type: 'string',
24+
values: [
25+
ModelCatalogTask.TEXT_GENERATION,
26+
ModelCatalogTask.TEXT_TO_TEXT,
27+
ModelCatalogTask.IMAGE_TO_TEXT,
28+
ModelCatalogTask.IMAGE_TEXT_TO_TEXT,
29+
ModelCatalogTask.VIDEO_TO_TEXT,
30+
ModelCatalogTask.AUDIO_TO_TEXT,
31+
],
32+
},
33+
[ModelCatalogStringFilterKey.LANGUAGE]: {
34+
type: 'string',
35+
values: [
36+
AllLanguageCode.AR,
37+
AllLanguageCode.CS,
38+
AllLanguageCode.DE,
39+
AllLanguageCode.EN,
40+
AllLanguageCode.ES,
41+
AllLanguageCode.FR,
42+
AllLanguageCode.IT,
43+
AllLanguageCode.JA,
44+
AllLanguageCode.KO,
45+
AllLanguageCode.NL,
46+
AllLanguageCode.PT,
47+
AllLanguageCode.ZH,
48+
],
49+
},
50+
},
51+
...partial,
52+
});

clients/ui/frontend/src/__tests__/cypress/cypress/pages/modelCatalog.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,31 @@
11
import { appChrome } from './appChrome';
22

3+
class ModelCatalogFilter {
4+
constructor(private title: string) {
5+
this.title = title;
6+
}
7+
8+
find() {
9+
return cy.findByTestId(`${this.title}-filter`);
10+
}
11+
12+
findCheckbox(value: string) {
13+
return this.find().findByTestId(`${this.title}-${value}-checkbox`);
14+
}
15+
16+
findShowMoreButton() {
17+
return this.find().findByTestId(`${this.title}-filter-show-more`);
18+
}
19+
20+
findShowLessButton() {
21+
return this.find().findByTestId(`${this.title}-filter-show-less`);
22+
}
23+
24+
findSearch() {
25+
return this.find().findByTestId(`${this.title}-filter-search`);
26+
}
27+
}
28+
329
class ModelCatalog {
430
visit() {
531
cy.visit('/model-catalog');
@@ -17,6 +43,26 @@ class ModelCatalog {
1743
cy.testA11y();
1844
}
1945

46+
findFilter(title: string) {
47+
return new ModelCatalogFilter(title).find();
48+
}
49+
50+
findFilterSearch(title: string) {
51+
return new ModelCatalogFilter(title).findSearch();
52+
}
53+
54+
findFilterShowMoreButton(title: string) {
55+
return new ModelCatalogFilter(title).findShowMoreButton();
56+
}
57+
58+
findFilterShowLessButton(title: string) {
59+
return new ModelCatalogFilter(title).findShowLessButton();
60+
}
61+
62+
findFilterCheckbox(title: string, value: string) {
63+
return new ModelCatalogFilter(title).findCheckbox(value);
64+
}
65+
2066
tabEnabled() {
2167
appChrome.findNavItem('Model Catalog').should('exist');
2268
return this;

clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/api.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ import type {
1212
RegisteredModelList,
1313
} from '~/app/types';
1414
import type {
15+
CatalogFilterOptionsList,
1516
CatalogModel,
16-
CatalogModelArtifactList,
17+
CatalogArtifactList,
1718
CatalogModelList,
1819
CatalogSourceList,
1920
} from '~/app/modelCatalogTypes';
@@ -157,7 +158,12 @@ declare global {
157158
((
158159
type: 'GET /api/:apiVersion/model_catalog/sources/:sourceId/artifacts/:modelName',
159160
options: { path: { apiVersion: string; sourceId: string; modelName: string } },
160-
response: ApiResponse<CatalogModelArtifactList>,
161+
response: ApiResponse<CatalogArtifactList>,
162+
) => Cypress.Chainable<null>) &
163+
((
164+
type: 'GET /api/:apiVersion/model_catalog/models/filter_options',
165+
options: { path: { apiVersion: string }; query: { namespace: string } },
166+
response: ApiResponse<CatalogFilterOptionsList>,
161167
) => Cypress.Chainable<null>);
162168
}
163169
}

clients/ui/frontend/src/__tests__/cypress/cypress/tests/mocked/modelCatalog/modelCatalog.cy.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from '~/__mocks__';
88
import type { CatalogSource } from '~/app/modelCatalogTypes';
99
import { MODEL_CATALOG_API_VERSION } from '~/__tests__/cypress/cypress/support/commands/api';
10+
import { mockCatalogFilterOptionsList } from '~/__mocks__/mockCatalogFilterOptionsList';
1011

1112
type HandlersProps = {
1213
sources?: CatalogSource[];
@@ -34,6 +35,15 @@ const initIntercepts = ({
3435
items: [mockCatalogModel({})],
3536
}),
3637
);
38+
39+
cy.interceptApi(
40+
`GET /api/:apiVersion/model_catalog/models/filter_options`,
41+
{
42+
path: { apiVersion: MODEL_CATALOG_API_VERSION },
43+
query: { namespace: 'kubeflow' },
44+
},
45+
mockCatalogFilterOptionsList(),
46+
);
3747
};
3848

3949
describe('Model Catalog Page', () => {
@@ -61,4 +71,49 @@ describe('Model Catalog Page', () => {
6171
modelCatalog.findPageDescription().should('be.visible');
6272
modelCatalog.findModelCatalogCards().should('have.length.at.least', 1);
6373
});
74+
75+
it('should display model catalog filters', () => {
76+
initIntercepts({});
77+
modelCatalog.visit();
78+
modelCatalog.navigate();
79+
modelCatalog.findFilter('Provider').should('be.visible');
80+
modelCatalog.findFilter('License').should('be.visible');
81+
modelCatalog.findFilter('Task').should('be.visible');
82+
modelCatalog.findFilter('Language').should('be.visible');
83+
});
84+
85+
it('filters show more and show less button should work', () => {
86+
initIntercepts({});
87+
modelCatalog.visit();
88+
modelCatalog.navigate();
89+
modelCatalog.findFilterShowMoreButton('Task').click();
90+
modelCatalog.findFilterCheckbox('Task', 'text-generation').should('be.visible');
91+
modelCatalog.findFilterCheckbox('Task', 'text-to-text').should('be.visible');
92+
modelCatalog.findFilterCheckbox('Task', 'image-to-text').should('be.visible');
93+
modelCatalog.findFilterCheckbox('Task', 'image-text-to-text').should('be.visible');
94+
modelCatalog.findFilterCheckbox('Task', 'audio-to-text').should('be.visible');
95+
modelCatalog.findFilterCheckbox('Task', 'video-to-text').should('be.visible');
96+
modelCatalog.findFilterShowLessButton('Task').click();
97+
modelCatalog.findFilterCheckbox('Task', 'audio-to-text').should('not.exist');
98+
});
99+
100+
it('filters should be searchable', () => {
101+
initIntercepts({});
102+
modelCatalog.visit();
103+
modelCatalog.navigate();
104+
modelCatalog.findFilterSearch('Task').type('audio-to-text');
105+
modelCatalog.findFilterCheckbox('Task', 'audio-to-text').should('be.visible');
106+
modelCatalog.findFilterCheckbox('Task', 'video-to-text').should('not.be.exist');
107+
});
108+
109+
// TODO: Add this test when the actual card filtering is implemented.
110+
// it('checkbox should work', () => {
111+
// initIntercepts({});
112+
// modelCatalog.visit();
113+
// modelCatalog.navigate();
114+
// modelCatalog.findFilterCheckbox('Task', 'text-generation').click();
115+
// modelCatalog.findFirstModelCatalogCard().should('be.visible');
116+
// modelCatalog.findFilterCheckbox('Task', 'text-to-text').click();
117+
// modelCatalog.findModelCatalogEmptyState().should('be.visible');
118+
// });
64119
});

0 commit comments

Comments
 (0)