-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Show flags page as modal when showFlags query parameter is set #5800
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
Show flags page as modal when showFlags query parameter is set #5800
Conversation
0610d2e to
f1914f6
Compare
9fe0aa0 to
2b693d4
Compare
8295782 to
1eee2a1
Compare
a4bbc17 to
b0b43b3
Compare
tensorboard/webapp/feature_flag/views/feature_flag_page.ng.html
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page_component.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page_component.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page_component.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page_container.ts
Outdated
Show resolved
Hide resolved
566656e to
cc56f03
Compare
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_component.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_component.ts
Outdated
Show resolved
Hide resolved
| this.featureFlagsDialog = this.dialog.open(FeatureFlagPageContainer); | ||
| this.featureFlagsDialog.afterClosed().subscribe(() => { | ||
| this.store.dispatch( | ||
| featureFlagOverrideChanged({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Can you add a comment explaining why you do this? (I guess you are just trying to keep the dialog from reopening?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll go ahead and add a comment. This is to stop the dialog from opening again after the page is refreshed as I imagine people do not want this to always appear.
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_component.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_module.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page_container.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page.ng.html
Outdated
Show resolved
Hide resolved
f43b878 to
935fc26
Compare
bmd3k
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a big change so I can only give you a partial review. I've reviewed everything in-depth except for the modal code and tests.
tensorboard/webapp/BUILD
Outdated
| "//tensorboard/webapp/customization:customization_test_lib", | ||
| "//tensorboard/webapp/deeplink:deeplink_test_lib", | ||
| "//tensorboard/webapp/feature_flag/effects:effects_test_lib", | ||
| "//tensorboard/webapp/feature_flag/views:views_test", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize this is confusing but feature_flag already has its own test_suite here:
https://cs.opensource.google/tensorflow/tensorboard/+/master:tensorboard/webapp/feature_flag/BUILD;l=64;drc=025eb00cef2bbcc48f28667f6819a7bec7e701a6
Can we move this there? And feature_flag/effects:effects_test_lib is duplicated in both test_suites so the one here should also be deleted
The rationale: At some point Stephan believed we should pile all tests into a single test_suite because there is overhead in bringing up the browser. But it turns out there is also overhead in running all your tests serially. So he started advocating for breaking up our test_suites. Feature flags is one of the few that has since been broken up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was in fact confusing but I'll go ahead and consolidate these. Out of curiosity do you have any idea as to why it has its own test suite? Looking deeper it seems there are actually 13 test suites?
tensorboard/webapp/feature_flag/views/feature_flag_page_container_test.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page_container_test.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_container.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_container.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_container_test.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_container_test.ts
Outdated
Show resolved
Hide resolved
| this.featureFlagsDialog = this.dialog.open(FeatureFlagPageContainer); | ||
| this.featureFlagsDialog.afterClosed().subscribe(() => { | ||
| // By disabling the flag when the dialog is closed to prevent it from appearing again after the page is refreshed. | ||
| this.store.dispatch( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking about this after playing with the code locally: I think when the dialog closes the page should be refreshed.
My reasoning: This is the first time a user can change feature flag state AFTER the app has been loaded. This probably opens up a ton of subtle bugs that we never had to consider in the past. We shouldn't allow the user to play with the app in this state. Refreshing the page after the dialog is closed would put the app back into a proper state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your logic on this but I'm not sure on the UX. It seems unintuitive that closing a modal would refresh the page.
Perhaps we should add a "Close and Refresh" button along with a message explaining why? I would really like to make the user aware of the page refresh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Let's address in a followup PR. The key is that users should not be able to use the app after changing the flag state.
| return; | ||
| } | ||
| if (this.featureFlagsDialog) { | ||
| this.featureFlagsDialog.close(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right. The only times I can imagine this code path being invoked are:
- User sets showFlags to false in the dialog.
- User presses "Reset All" in the dialog.
In neither case would I expect the dialog to be immediately closed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it depends on how you think about showFlags. If you really think it is a feature flag then it makes sense that it should close, but I can definitely see why you would view that as confusing so I'll go ahead and remove this.
243b8a0 to
79fb1d8
Compare
bmd3k
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I think the rest looks pretty good. I think the biggest thing I'd recommend is to reorganize/refactor the feature_flag_page tests a bit to adhere to team norms. Otherwise just some nits.
| this.featureFlagsDialog = this.dialog.open(FeatureFlagPageContainer); | ||
| this.featureFlagsDialog.afterClosed().subscribe(() => { | ||
| // By disabling the flag when the dialog is closed to prevent it from appearing again after the page is refreshed. | ||
| this.store.dispatch( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. Let's address in a followup PR. The key is that users should not be able to use the app after changing the flag state.
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_container_test.ts
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_modal_trigger_container_test.ts
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page.ng.html
Outdated
Show resolved
Hide resolved
tensorboard/webapp/feature_flag/views/feature_flag_page.ng.html
Outdated
Show resolved
Hide resolved
| return fixture.nativeElement.querySelector('feature-flag-page-component'); | ||
| } | ||
|
|
||
| it('creates rows for each feature flag', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Normally we prefer to have a single test file for each container/component combination. We think it strikes a good balance between writing isolated tests but also ensuring a particular container/component combination works as expected when integrated together. The fact that they are split into a container/component is (mostly) an implementation detail that we hide from the tests.
In this case we would have a feature_flag_page_test instead of both feature_flag_page_component_test and feature_flag_page_container_test.
I mention it here specifically because for these tests where you introduce TestableComponent, you could instead test by constructing a FeatureFlagPageContainer and specifying appropriate values for your ngrx selectors and then checking that the UI renders as expected.
| store.overrideSelector(getDefaultFeatureFlags, {} as FeatureFlags); | ||
| store.overrideSelector(getOverriddenFeatureFlags, {}); | ||
| const component = getComponent(); | ||
| component.onFlagChanged({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again in the context of a single test file for each container/component combination, we generally prefer to test dispatching actions starting at the UI. That is: setup your ngrx selectors as appropriate, render the container/component/UI, interact with an element, verify the action is fired.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've merged the files and refactored the tests to move away from componentInstance except in the instance of formatFlagValue tests which I believe are significantly simpler as is.
tensorboard/webapp/feature_flag/views/feature_flag_page_component_test.ts
Outdated
Show resolved
Hide resolved
| @Component({ | ||
| selector: 'feature-flag-page-component', | ||
| styleUrls: ['feature_flag_page_style.css'], | ||
| templateUrl: `feature_flag_page.ng.html`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style and template files should be named feature_flag_page_component.css and feature_flag_page_component.ng.html. "style" is redundant (in our opinion). we include "component" because it differentiates them from possible "container" css and html files, too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright. I didn't actually create either file but I'll go ahead and rename them.
0eecf96 to
9eff1bb
Compare
JamesHollyer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is getting pretty close. In the future try to split up PRs into smaller chunks for review.
| imports: [CommonModule, FeatureFlagPageModule], | ||
| providers: [ | ||
| provideMockStore({ | ||
| initialState: buildState( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative to constructing initialState like this that we usually use is to just override all the selectors that the components rely on. I'm fine with what you have since its simple but just wanted to let you know in case you find that simpler in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried that initially and I was getting errors from the selectors indicating that state was undefined.
| const dataCells = component.querySelectorAll('td'); | ||
| expect(dataCells.length).toEqual(3); | ||
| const selectors = component.querySelectorAll('mat-select'); | ||
| expect(selectors.length).toEqual(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worth to make the tests a litlte stricter - ie actually verify that the "inColab" row has a mat-select while the "enabledExperimentalPlugins" is marked as unsupported? The test is unusually vague about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, this is pretty vague. I've gone ahead and added another expect checking the value of the td which I think should be specific enough.
45f88fa to
27003ba
Compare
…odal to wrapper component to show flags page when the showFlags query parameter is set
27003ba to
4093f4b
Compare
…rflow#5800) * Add new feature flag to control showing the feature flag modal, add modal to wrapper component to show flags page when the showFlags query parameter is set
…rflow#5800) * Add new feature flag to control showing the feature flag modal, add modal to wrapper component to show flags page when the showFlags query parameter is set
I'm breaking this change into two parts and blocking this with #5868
Description of Changes
Add new feature flag to control showing the feature flag modal, add modal to wrapper component to show flags page when the showFlags query parameter is set.
Currently blocked by 5717Motivation for features / changes
As of 5760 we have a
/flagspage which is not accessible due to the backend routing code.Last week I suggested updating the routing code to enable
/flagsto be shown (5796). And it was decided that it was undesirable to complicate the routing code and thus decided to show the page as a modal instead.Technical description of changes
I've chosen to show the
FlagsPageContaineras the contents of aMatDialogmodal based on the status of theenableShowFlagsfeature flag which is controlled by the query parametershowFlags.Given that we want to use the modal as an alternative to a route, it should appear on all pages and that the modal is unrelated to any existing part of the page, I have elected to create and manage it as part of the page wrapper.
Screenshots of UI changes
showFlags=trueshowFlagsnot setshowFlags=trueorshowFlags.showFlags=falseand assert the modal no longer appears.