Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 419
Added ListModels functionality for Firebase ML#795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -45,6 +45,13 @@ export interface ModelUpdateOptions extends ModelOptions { | ||
| state?: { published?: boolean; }; | ||
| } | ||
| /** Interface representing listModels options. */ | ||
| export interface ListModelsOptions { | ||
| filter?: string; | ||
| pageSize?: number; | ||
| pageToken?: string; | ||
| } | ||
| export interface ModelContent { | ||
| readonly displayName?: string; | ||
| readonly tags?: string[]; | ||
| @@ -66,6 +73,11 @@ export interface ModelResponse extends ModelContent { | ||
| readonly modelHash?: string; | ||
| } | ||
| export interface ListModelsResponse { | ||
| readonly models?: ModelResponse[]; | ||
| readonly nextPageToken?: string; | ||
| } | ||
| export interface OperationResponse { | ||
| readonly name?: string; | ||
| readonly done: boolean; | ||
| @@ -140,6 +152,42 @@ export class MachineLearningApiClient { | ||
| }); | ||
| } | ||
| public listModels(options: ListModelsOptions = {}): Promise<ListModelsResponse> { | ||
| if (!validator.isNonNullObject(options)) { | ||
| const err = new FirebaseMachineLearningError('invalid-argument', 'Invalid ListModelsOptions'); | ||
| return Promise.reject(err); | ||
| } | ||
| if (typeof options.filter !== 'undefined' && !validator.isNonEmptyString(options.filter)) { | ||
hiranya911 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| const err = new FirebaseMachineLearningError('invalid-argument', 'Invalid list filter.'); | ||
| return Promise.reject(err); | ||
| } | ||
| if (typeof options.pageSize !== 'undefined') { | ||
| if (!validator.isNumber(options.pageSize)) { | ||
| const err = new FirebaseMachineLearningError('invalid-argument', 'Invalid page size.'); | ||
| return Promise.reject(err); | ||
| } | ||
| if (options.pageSize < 1 || options.pageSize > 100) { | ||
| const err = new FirebaseMachineLearningError( | ||
| 'invalid-argument', 'Page size must be between 1 and 100.'); | ||
| return Promise.reject(err); | ||
| } | ||
| } | ||
| if (typeof options.pageToken !== 'undefined' && !validator.isNonEmptyString(options.pageToken)) { | ||
| const err = new FirebaseMachineLearningError( | ||
| 'invalid-argument', 'Next page token must be a non-empty string.'); | ||
| return Promise.reject(err); | ||
| } | ||
| return this.getUrl() | ||
| .then((url) => { | ||
| const request: HttpRequestConfig = { | ||
| method: 'GET', | ||
| url: `${url}/models`, | ||
| data: options, | ||
| }; | ||
| return this.sendRequest<ListModelsResponse>(request); | ||
| }); | ||
| } | ||
| public deleteModel(modelId: string): Promise<void> { | ||
| return this.getUrl() | ||
| .then((url) => { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -171,9 +171,7 @@ describe('admin.machineLearning', () => { | ||
| }); | ||
| it('sets tags for a model', () => { | ||
| // TODO(ifielker): Uncomment & replace when BE change lands. | ||
| // const ORIGINAL_TAGS = ['tag-node-update-1']; | ||
| const ORIGINAL_TAGS: string[] = []; | ||
| const ORIGINAL_TAGS = ['tag-node-update-1']; | ||
| const NEW_TAGS = ['tag-node-update-2', 'tag-node-update-3']; | ||
| return createTemporaryModel({ | ||
| @@ -192,7 +190,7 @@ describe('admin.machineLearning', () => { | ||
| }); | ||
| it('updates the tflite file', () => { | ||
| Promise.all([ | ||
| return Promise.all([ | ||
| createTemporaryModel(), | ||
| uploadModelToGcs('model1.tflite', 'valid_model.tflite')]) | ||
| .then(([model, fileName]) => { | ||
| @@ -324,6 +322,120 @@ describe('admin.machineLearning', () => { | ||
| }); | ||
| }); | ||
| describe('listModels()', () => { | ||
hiranya911 marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| let model1: admin.machineLearning.Model; | ||
| let model2: admin.machineLearning.Model; | ||
| let model3: admin.machineLearning.Model; | ||
| before(() => { | ||
| return Promise.all([ | ||
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Loop might be slightly more readable here: But it's your call. | ||
| admin.machineLearning().createModel({ | ||
| displayName: 'node-integration-list1', | ||
| tags: ['node-integration-tag-1'], | ||
| }), | ||
| admin.machineLearning().createModel({ | ||
| displayName: 'node-integration-list2', | ||
| tags: ['node-integration-tag-1'], | ||
| }), | ||
| admin.machineLearning().createModel({ | ||
| displayName: 'node-integration-list3', | ||
| tags: ['node-integration-tag-1'], | ||
| })]) | ||
| .then(([m1, m2, m3]: admin.machineLearning.Model[]) => { | ||
| model1 = m1; | ||
| model2 = m2; | ||
| model3 = m3; | ||
| }); | ||
| }); | ||
| after(() => { | ||
| return Promise.all([ | ||
| admin.machineLearning().deleteModel(model1.modelId), | ||
| admin.machineLearning().deleteModel(model2.modelId), | ||
| admin.machineLearning().deleteModel(model3.modelId), | ||
| ]); | ||
| }); | ||
| it('resolves with a list of models', () => { | ||
| return admin.machineLearning().listModels({pageSize: 100}) | ||
| .then((modelList) => { | ||
| expect(modelList.models.length).to.be.at.least(2); | ||
| expect(modelList.models).to.deep.include(model1); | ||
| expect(modelList.models).to.deep.include(model2); | ||
| expect(modelList.pageToken).to.be.empty; | ||
| }); | ||
| }); | ||
| it('respects page size', () => { | ||
| return admin.machineLearning().listModels({pageSize: 2}) | ||
| .then((modelList) => { | ||
| expect(modelList.models.length).to.equal(2); | ||
| expect(modelList.pageToken).not.to.be.empty; | ||
| }); | ||
| }); | ||
| it('filters by exact displayName', () => { | ||
| return admin.machineLearning().listModels({filter: 'displayName=node-integration-list1'}) | ||
| .then((modelList) => { | ||
| expect(modelList.models.length).to.equal(1); | ||
| expect(modelList.models[0]).to.deep.equal(model1); | ||
| expect(modelList.pageToken).to.be.empty; | ||
| }); | ||
| }); | ||
| it('filters by displayName prefix', () => { | ||
| return admin.machineLearning().listModels({filter: 'displayName:node-integration-list*', pageSize: 100}) | ||
| .then((modelList) => { | ||
| expect(modelList.models.length).to.be.at.least(3); | ||
| expect(modelList.models).to.deep.include(model1); | ||
| expect(modelList.models).to.deep.include(model2); | ||
| expect(modelList.models).to.deep.include(model3); | ||
| expect(modelList.pageToken).to.be.empty; | ||
| }); | ||
| }); | ||
| it('filters by tag', () => { | ||
| return admin.machineLearning().listModels({filter: 'tags:node-integration-tag-1', pageSize: 100}) | ||
| .then((modelList) => { | ||
| expect(modelList.models.length).to.be.at.least(3); | ||
| expect(modelList.models).to.deep.include(model1); | ||
| expect(modelList.models).to.deep.include(model2); | ||
| expect(modelList.models).to.deep.include(model3); | ||
| expect(modelList.pageToken).to.be.empty; | ||
| }); | ||
| }); | ||
| it('handles pageTokens properly', () => { | ||
| return admin.machineLearning().listModels({filter: 'displayName:node-integration-list*', pageSize: 2}) | ||
| .then((modelList) => { | ||
| expect(modelList.models.length).to.equal(2); | ||
| expect(modelList.pageToken).not.to.be.empty; | ||
| return admin.machineLearning().listModels({ | ||
| filter: 'displayName:node-integration-list*', | ||
| pageSize: 2, | ||
| pageToken: modelList.pageToken}) | ||
| .then((modelList2) => { | ||
| expect(modelList2.models.length).to.be.at.least(1); | ||
| expect(modelList2.pageToken).to.be.empty; | ||
| }); | ||
| }); | ||
| }); | ||
| it('successfully returns an empty list of models', () => { | ||
| return admin.machineLearning().listModels({filter: 'displayName=non-existing-model'}) | ||
| .then((modelList) => { | ||
| expect(modelList.models.length).to.equal(0); | ||
| expect(modelList.pageToken).to.be.empty; | ||
| }); | ||
| }); | ||
| it('rejects with invalid argument if the filter is invalid', () => { | ||
| return admin.machineLearning().listModels({filter: 'invalidFilterItem=foo'}) | ||
| .should.eventually.be.rejected.and.have.property( | ||
| 'code', 'machine-learning/invalid-argument'); | ||
| }); | ||
| }); | ||
| describe('deleteModel()', () => { | ||
| it('rejects with not-found when the Model does not exist', () => { | ||
| const nonExistingName = '00000000'; | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.