Add navigation menu with in compose up (attached)#11605
Conversation
| case keyboard.KeyCtrlC: | ||
| keyboard.Close() | ||
| formatter.KeyboardInfo.ClearInfo() | ||
| gracefulTeardown() |
There was a problem hiding this comment.
I wonder about the impact doing so as context is not cancelled.
There was a problem hiding this comment.
How can I test this scenario? What is expected if the context is not cancelled?
There was a problem hiding this comment.
I'm not sure. We rely on <-ctx.Done() in a few place to stop goroutines after Ctrl+C, typically to stop collecting logs, watch container events, etc. I assume after the application stopped those will be released anyway, but this might bring some new race conditions.
There was a problem hiding this comment.
I think it should be okay here (although it makes me think the graceful termination code/doneCh/ctx.Done() handling could use a refactor).
I wonder if cancelling the context here would be better.
Also I wonder, since we're already capturing SIGINT above (when we do signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM), if the new keyboard stuff is going to interfere with that.
There was a problem hiding this comment.
Changed the way we handle the tear down. I am sending a SIGTERM signal to signalChan https://github.com/docker/compose/pull/11605/files#diff-f5beca66c4f279fbf26bb1aad4367b541d1b0096b5e68d03515fb922201e7aadR290
| github.com/docker/docker-credential-helpers v0.8.0 // indirect | ||
| github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect | ||
| github.com/docker/go-metrics v0.0.1 // indirect | ||
| github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 |
There was a problem hiding this comment.
Probably does what we expect, by I wonder we want to rely on a dependency which hasn't been updated for 2 years
6c22546 to
18bcf36
Compare
| func (lk *LogKeyboard) createBuffer() { | ||
| fmt.Print("\012") // new line | ||
| fmt.Print("\012") | ||
| fmt.Print("\033[2A") // go back 3 lines | ||
| } | ||
|
|
||
| func (lk *LogKeyboard) printInfo() { | ||
| height := goterm.Height() | ||
| fmt.Print("\0337") // save cursor position | ||
| if lk.err != nil { | ||
| fmt.Printf("\033[%d;0H", height-1) // Move to before last line | ||
| fmt.Printf("\033[K" + errorColor + "[Error] " + lk.err.Error()) | ||
| } | ||
| fmt.Printf("\033[%d;0H", height) // Move to last line | ||
| // clear line | ||
| fmt.Print("\033[K" + navColor(" >> [CTRL+G] open project in Docker Desktop [$] get more features")) | ||
| fmt.Print("\0338") // restore cursor position | ||
| } | ||
|
|
||
| func (lk *LogKeyboard) ClearInfo() { | ||
| height := goterm.Height() | ||
| fmt.Print("\0337") // save cursor position | ||
| if lk.err != nil { | ||
| fmt.Printf("\033[%d;0H", height-1) | ||
| fmt.Print("\033[2K") // clear line | ||
| } | ||
| fmt.Printf("\033[%d;0H", height) // Move to last line | ||
| fmt.Print("\033[2K") // clear line | ||
| fmt.Print("\0338") // restore cursor position | ||
| } |
There was a problem hiding this comment.
Just thinking/not a blocker, but I'm starting to wonder if we should think about migrating over to an actual TUI lib as opposed to rolling our own. bubbletea is pretty nice for this kind of stuff.
There was a problem hiding this comment.
agreed, we should consider this lib for a further interaction if this feature gets traction
| fmt.Print("\033[2K") // clear line | ||
| } | ||
| fmt.Printf("\033[%d;0H", height) // Move to last line | ||
| fmt.Print("\033[2K") // clear line |
There was a problem hiding this comment.
Might be more readable to put all these hieroglyphs in consts
There was a problem hiding this comment.
Did huge refactor on this file. Was still work in progress at the time. It should be more readable now :)
|
Looks really good overall! Happy with the "TUI" improvements 🥳 I think that's the plan from what @jhrotko has mentioned, but I wanted to confirm that the idea is to use #11593 and only enable this when running this on a Docker Desktop environment (cc @milas), so that people running this headless/on bare Linux w/o DD don't get confused. |
3bafd93 to
3a009bc
Compare
| flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.") | ||
| flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy") | ||
| flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.") | ||
| flags.BoolVar(&up.navigationBar, "navigation-menu", true, "While running in attach mode, enable shortcuts and shortcuts info bar.") |
There was a problem hiding this comment.
"shortcuts and shortcuts" ?
There was a problem hiding this comment.
Did a fix. Let me know what do you think
1bbc729 to
ae6b3fa
Compare
85dd5e0 to
9dc8a12
Compare
b0d1d2d to
11189b2
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #11605 +/- ##
==========================================
- Coverage 55.62% 54.75% -0.87%
==========================================
Files 142 145 +3
Lines 12258 12553 +295
==========================================
+ Hits 6818 6874 +56
- Misses 4752 4984 +232
- Partials 688 695 +7 ☔ View full report in Codecov by Sentry. |
ce514e9 to
63fb90c
Compare
| FROM build-base AS test | ||
| ARG CGO_ENABLED=0 | ||
| ARG BUILD_TAGS | ||
| ENV COMPOSE_MENU=FALSE |
There was a problem hiding this comment.
Is this necessary for building?
There was a problem hiding this comment.
yes, because by default it should be true, in order not to break tests this is easier to set the COMPOSE_MENU false in all containers
There was a problem hiding this comment.
(as a follow-up) We can have the e2e tests set this when they exec Compose to avoid needing it globally like this
| return fmt.Sprintf("%s%s%s", ansiColorCode(code), s, ansiColorCode("0")) | ||
| } | ||
|
|
||
| func ansi(code string) string { | ||
| // Everything about ansiColorCode color https://hyperskill.org/learn/step/18193 | ||
| func ansiColorCode(code string) string { | ||
| return fmt.Sprintf("\033[%sm", code) | ||
| } |
There was a problem hiding this comment.
Didn't realize we had this – isn't the stuff in aec enough? I think we use it sometimes
compose/pkg/progress/colors.go
Lines 30 to 36 in 9b0e3d5
There was a problem hiding this comment.
I need to add the bold to the prefix color :( \033[36m -> cyan vs \033[1;36m -> bold + cyan. The prefix color is not abstract enough for me to add the bold format
43a5fb5 to
aec5805
Compare
68d5908 to
d0ce3bb
Compare
| if err != nil { | ||
| return err | ||
| } | ||
| if !upOptions.navigationMenuChanged { |
There was a problem hiding this comment.
seems bit complicated to manage value set by env var, is there any drawback doing the same as https://github.com/docker/compose/blob/main/cmd/compose/up.go#L109-L110 ?
There was a problem hiding this comment.
(this comment is mine, I was connected with by @docker.com email while commenting :P)
There was a problem hiding this comment.
(not for this PR)
We definitely need to figure out a re-usable pattern for these types of use cases to simplify driving experimental settings via Docker Desktop and combining with flags/options in Compose
Our init sequence has gotten pretty complex between Docker CLI plugin framework + Cobra + Compose itself 😓
c7f443b to
33b4f1d
Compare
milas
left a comment
There was a problem hiding this comment.
LGTM - few random comments, biggest question is over that one conditional but otherwise no major concerns from me
| FROM build-base AS test | ||
| ARG CGO_ENABLED=0 | ||
| ARG BUILD_TAGS | ||
| ENV COMPOSE_MENU=FALSE |
There was a problem hiding this comment.
(as a follow-up) We can have the e2e tests set this when they exec Compose to avoid needing it globally like this
| if err != nil { | ||
| return err | ||
| } | ||
| if !upOptions.navigationMenuChanged { |
There was a problem hiding this comment.
(not for this PR)
We definitely need to figure out a re-usable pattern for these types of use cases to simplify driving experimental settings via Docker Desktop and combining with flags/options in Compose
Our init sequence has gotten pretty complex between Docker CLI plugin framework + Cobra + Compose itself 😓
| timeStart time.Time | ||
| } | ||
|
|
||
| func (ke *KeyboardError) shoudlDisplay() bool { |
There was a problem hiding this comment.
s/shoudlDisplay/shouldDisplay
|
|
||
| MoveCursor(height-linesOffset(menu), 0) | ||
| ClearLine() | ||
| fmt.Print(menu) |
There was a problem hiding this comment.
(for follow-up)
You can make it easier to test this by storing stdout/io.Writer on the type and writing to that.
There was a problem hiding this comment.
you mean, instead of using fmt.Print first pipe all the characters to stdout and then print?
There was a problem hiding this comment.
add an io.Writer field in KeyboardError type, and use fmt.Fprint. This will allow unit tests to pass a bytes.Buffer as writer and check output
For actual use, should be initialized to dockercli.Out() to respect docker CLI plugin architecture
| } | ||
| } | ||
|
|
||
| func (lk *LogKeyboard) navigationMenu() string { |
There was a problem hiding this comment.
fyi strings.Builder can sometimes be handy for these types of finicky formatting formatting type functions
(this is readable/fine as-is! no need to change, just sharing)
https://pkg.go.dev/strings#Builder
| lk.Watch.switchWatching() | ||
| if !lk.Watch.isWatching() && lk.Watch.Cancel != nil { | ||
| lk.Watch.Cancel() | ||
| } else { |
There was a problem hiding this comment.
should this be
lk.Watch.switchWatching()
if !lk.Watch.isWatching() {
if lk.Watch.Cancel != nil {
lk.Watch.Cancel()
}
} else {(that is, even if the cancel function is nil, we only want to start watch if isWatching == true, right?)
There was a problem hiding this comment.
yes, I think this was an old bug that was fixed, I think it was because the configuration validation was being done after and not before at some point. Both expressions are equivalent
|
|
||
| } | ||
|
|
||
| func (s *composeService) isDesktopIntegrationActive() bool { |
There was a problem hiding this comment.
I refactored other places in the code that also did this validation
| }) | ||
|
|
||
| if options.Start.Watch { | ||
| if options.Start.Watch && !options.Start.NavigationMenu { |
There was a problem hiding this comment.
IIUC --watch would then not start watch mode until menu is disabled. Why ?
There was a problem hiding this comment.
because this is being managed inside Keyboard Manager, I need the context to start/stop watch mode afterwards
There was a problem hiding this comment.
If the --watch is passed and the menu is active, this will start the watch command here https://github.com/docker/compose/pull/11605/files#diff-809980e7b57a811e276a5d7951e759d3071d157ebad94f0a52370476a1fbfcedR103-R105
There was a problem hiding this comment.
right. yet more spaghetti code to our already confusing "architecture" 😅
| return fmt.Errorf("none of the selected services is configured for watch, consider setting an 'develop' section") | ||
| } | ||
| options.LogTo.Log(api.WatchLogger, "watch enabled") | ||
| options.LogTo.Log(api.WatchLogger, "Watch started") |
There was a problem hiding this comment.
I personally preferred "enabled", but that's just me
There was a problem hiding this comment.
I agree, even the navigation menu message switches between watch enabled/disabled
4ed8a30 to
fc8b195
Compare
Signed-off-by: Joana Hrotko <[email protected]>
fc8b195 to
831a5af
Compare

What I did
We want to open Docker Desktop through a shortcut in
docker compose up.To use this feature run
... yes it comes as default for attached mode :). it will render the navigation menu at the bottom.
You can disable this feature while using
uprun:OR set environment var
COMPOSE_MENU=[false|true]OR use Docker Desktop experimental features and enable/disable -
Compose: TUI nav barScreen.Recording.2024-03-16.at.17.01.26.mov
When an error occurs, it will be displayed at the top of the navigation menu for 10s.
We are also sending telemetry to understand if users' usage of this new feature. PR merged in pinata: https://github.com/docker/pinata/pull/27207
This PR introduces a keyboard listener that is currently listening to the following events:
docker compose up --watch)docker-desktop://dashboard/apps/NAMERelated issue
https://docker.atlassian.net/jira/software/c/projects/COMP/boards/576?selectedIssue=COMP-417
(not mandatory) A picture of a cute animal, if possible in relation to what you did