-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds mechanism to include feature flags in Angular HTTP requests. (#5718
) This adds a mechanism that dumps the client-side FeatureFlag object into each HTTP request that originates from the TB Angular code base. (Googlers, see: http://go/tb-client-feature-flags-in-data-provider for the motivation and high level design.) The data is dumped into a custom header. The format is effectively: X-TensorBoard-Feature-Flags: JSON.stringify(currentFeatureFlags) We implement this by introducing an HttpInterceptor. It allows us to intercept each request in the Angular HTTP pipeline and inject the new header. The HttpInterceptor is provided in the existing FeatureFlagModule so all instances of TensorBoard will receive this behavior without any additional configuration. Additional things worth noting: * This will send ALL feature flags in the request header rather than a curated subset of them. In a subsequent PR, we'll add support to mark certain feature flags as "sendToServer" and only send that subset. This PR unblocks the main use case at the cost of there being some temporary overhead in HTTP requests. * In a subsequent PR We will add similar functionality to the TB Polymer code base. * In a subsequent PR we will add support on the TB HTTP Server to consume these feature flags and trigger logic based on the values. * I moved feature_flags tests into their own "karma_test" target separate from the big top-level karma_test target. stephanwlee advocated for breaking up the top-level karma_test target in the months before he left the team.
- Loading branch information
Showing
7 changed files
with
194 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
load("//tensorboard/defs:defs.bzl", "tf_ng_module", "tf_ts_library") | ||
|
||
package(default_visibility = ["//tensorboard:internal"]) | ||
|
||
tf_ng_module( | ||
name = "http", | ||
srcs = [ | ||
"feature_flag_http_interceptor.ts", | ||
], | ||
deps = [ | ||
"//tensorboard/webapp/angular:expect_angular_common_http", | ||
"//tensorboard/webapp/feature_flag/store", | ||
"//tensorboard/webapp/feature_flag/store:types", | ||
"@npm//@angular/core", | ||
"@npm//@ngrx/store", | ||
"@npm//rxjs", | ||
], | ||
) | ||
|
||
tf_ts_library( | ||
name = "http_test_lib", | ||
testonly = True, | ||
srcs = [ | ||
"feature_flag_http_interceptor_test.ts", | ||
], | ||
deps = [ | ||
":http", | ||
"//tensorboard/webapp/angular:expect_angular_common_http", | ||
"//tensorboard/webapp/angular:expect_angular_common_http_testing", | ||
"//tensorboard/webapp/angular:expect_angular_core_testing", | ||
"//tensorboard/webapp/angular:expect_ngrx_store_testing", | ||
"//tensorboard/webapp/feature_flag:testing", | ||
"//tensorboard/webapp/feature_flag:types", | ||
"//tensorboard/webapp/feature_flag/store", | ||
"//tensorboard/webapp/feature_flag/store:types", | ||
"@npm//@angular/core", | ||
"@npm//@ngrx/effects", | ||
"@npm//@ngrx/store", | ||
"@npm//@types/jasmine", | ||
"@npm//rxjs", | ||
], | ||
) |
58 changes: 58 additions & 0 deletions
58
tensorboard/webapp/feature_flag/http/feature_flag_http_interceptor.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
==============================================================================*/ | ||
import { | ||
HttpEvent, | ||
HttpHandler, | ||
HttpInterceptor, | ||
HttpRequest, | ||
} from '@angular/common/http'; | ||
import {Injectable} from '@angular/core'; | ||
import {select, Store} from '@ngrx/store'; | ||
import {Observable} from 'rxjs'; | ||
import {first, switchMap} from 'rxjs/operators'; | ||
import {getFeatureFlags} from '../store/feature_flag_selectors'; | ||
import {State as FeatureFlagState} from '../store/feature_flag_types'; | ||
|
||
export const FEATURE_FLAGS_HEADER_NAME = 'X-TensorBoard-Feature-Flags'; | ||
|
||
/** | ||
* HttpInterceptor for injecting feature flags into each HTTP request | ||
* originating from the Angular TensorBoard code base. | ||
*/ | ||
@Injectable() | ||
export class FeatureFlagHttpInterceptor implements HttpInterceptor { | ||
constructor(private readonly store: Store<FeatureFlagState>) {} | ||
|
||
intercept( | ||
request: HttpRequest<unknown>, | ||
next: HttpHandler | ||
): Observable<HttpEvent<unknown>> { | ||
return this.store.pipe( | ||
select(getFeatureFlags), | ||
first(), | ||
switchMap((featureFlags) => { | ||
// Add feature flags to the headers. | ||
request = request.clone({ | ||
headers: request.headers.set( | ||
FEATURE_FLAGS_HEADER_NAME, | ||
JSON.stringify(featureFlags) | ||
), | ||
}); | ||
// Delegate to next Interceptor. | ||
return next.handle(request); | ||
}) | ||
); | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
tensorboard/webapp/feature_flag/http/feature_flag_http_interceptor_test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
/* Copyright 2022 The TensorFlow Authors. All Rights Reserved. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
==============================================================================*/ | ||
|
||
import {HttpClient, HttpHeaders, HTTP_INTERCEPTORS} from '@angular/common/http'; | ||
import { | ||
HttpClientTestingModule, | ||
HttpTestingController, | ||
} from '@angular/common/http/testing'; | ||
import {TestBed} from '@angular/core/testing'; | ||
import {provideMockActions} from '@ngrx/effects/testing'; | ||
import {Store} from '@ngrx/store'; | ||
import {MockStore, provideMockStore} from '@ngrx/store/testing'; | ||
import {of} from 'rxjs'; | ||
import {getFeatureFlags} from '../store/feature_flag_selectors'; | ||
import {State as FeatureFlagState} from '../store/feature_flag_types'; | ||
import {buildFeatureFlag} from '../testing'; | ||
import {FeatureFlagHttpInterceptor} from './feature_flag_http_interceptor'; | ||
|
||
describe('FeatureFlagHttpInterceptor', () => { | ||
let store: MockStore<FeatureFlagState>; | ||
let httpClient: HttpClient; | ||
|
||
beforeEach(async () => { | ||
await TestBed.configureTestingModule({ | ||
imports: [HttpClientTestingModule], | ||
providers: [ | ||
provideMockActions(() => of()), | ||
provideMockStore(), | ||
{ | ||
provide: HTTP_INTERCEPTORS, | ||
useClass: FeatureFlagHttpInterceptor, | ||
multi: true, | ||
}, | ||
], | ||
}).compileComponents(); | ||
|
||
store = TestBed.inject<Store<FeatureFlagState>>( | ||
Store | ||
) as MockStore<FeatureFlagState>; | ||
store.overrideSelector(getFeatureFlags, buildFeatureFlag()); | ||
|
||
// Note that we do not test FeatureFlagHttpInterceptor directly. We instead | ||
// test it indirectly by firing Http requests and examining the final | ||
// request recorded by the HttpTestingController. | ||
httpClient = TestBed.inject(HttpClient); | ||
}); | ||
|
||
it('injects feature flags into the HTTP request', () => { | ||
store.overrideSelector(getFeatureFlags, buildFeatureFlag({inColab: true})); | ||
httpClient.get('/data/hello').subscribe(); | ||
const request = TestBed.inject(HttpTestingController).expectOne( | ||
'/data/hello' | ||
); | ||
expect(request.request.headers).toEqual( | ||
new HttpHeaders().set( | ||
'X-TensorBoard-Feature-Flags', | ||
JSON.stringify(buildFeatureFlag({inColab: true})) | ||
) | ||
); | ||
}); | ||
}); |