-
Notifications
You must be signed in to change notification settings - Fork 5.2k
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
Add navigation menu with in compose up (attached) #11605
Conversation
pkg/compose/up.go
Outdated
case keyboard.KeyCtrlC: | ||
keyboard.Close() | ||
formatter.KeyboardInfo.ClearInfo() | ||
gracefulTeardown() |
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 wonder about the impact doing so as context is not cancelled.
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.
How can I test this scenario? What is expected if the context is not cancelled?
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'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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -81,6 +83,7 @@ require ( | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
cmd/formatter/shortcut.go
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agreed, we should consider this lib for a further interaction if this feature gets traction
cmd/formatter/shortcut.go
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be more readable to put all these hieroglyphs in const
s
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.
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
cmd/compose/up.go
Outdated
@@ -127,6 +128,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"shortcuts and shortcuts" ?
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.
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
@@ -101,6 +101,7 @@ RUN --mount=type=bind,target=. \ | |||
FROM build-base AS test | |||
ARG CGO_ENABLED=0 | |||
ARG BUILD_TAGS | |||
ENV COMPOSE_MENU=FALSE |
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.
Is this necessary for building?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(as a follow-up) We can have the e2e tests set this when they exec Compose to avoid needing it globally like this
cmd/formatter/colors.go
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
DoneColor colorFunc = aec.BlueF.Apply | |
TimerColor colorFunc = aec.BlueF.Apply | |
CountColor colorFunc = aec.YellowF.Apply | |
WarningColor colorFunc = aec.YellowF.With(aec.Bold).Apply | |
SuccessColor colorFunc = aec.GreenF.Apply | |
ErrorColor colorFunc = aec.RedF.With(aec.Bold).Apply | |
PrefixColor colorFunc = aec.CyanF.Apply |
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.
then it's used like
Line 258 in 9b0e3d5
SuccessColor(strings.Join(completion, "")), |
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 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
@@ -181,6 +186,9 @@ func runUp( | |||
if err != nil { | |||
return err | |||
} | |||
if !upOptions.navigationMenuChanged { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(this comment is mine, I was connected with by @docker.com email while commenting :P)
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.
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.
oh indeed
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.
(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
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.
LGTM - few random comments, biggest question is over that one conditional but otherwise no major concerns from me
@@ -101,6 +101,7 @@ RUN --mount=type=bind,target=. \ | |||
FROM build-base AS test | |||
ARG CGO_ENABLED=0 | |||
ARG BUILD_TAGS | |||
ENV COMPOSE_MENU=FALSE |
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.
(as a follow-up) We can have the e2e tests set this when they exec Compose to avoid needing it globally like this
@@ -181,6 +186,9 @@ func runUp( | |||
if err != nil { | |||
return err | |||
} | |||
if !upOptions.navigationMenuChanged { |
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.
(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 😓
cmd/formatter/shortcut.go
Outdated
timeStart time.Time | ||
} | ||
|
||
func (ke *KeyboardError) shoudlDisplay() bool { |
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.
s/shoudlDisplay/shouldDisplay
|
||
MoveCursor(height-linesOffset(menu), 0) | ||
ClearLine() | ||
fmt.Print(menu) |
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.
(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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean, instead of using fmt.Print first pipe all the characters to stdout and then print?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
cmd/formatter/shortcut.go
Outdated
lk.Watch.switchWatching() | ||
if !lk.Watch.isWatching() && lk.Watch.Cancel != nil { | ||
lk.Watch.Cancel() | ||
} else { |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
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.
nice catch
@@ -320,3 +320,7 @@ func (s *composeService) RuntimeVersion(ctx context.Context) (string, error) { | |||
return runtimeVersion.val, runtimeVersion.err | |||
|
|||
} | |||
|
|||
func (s *composeService) isDesktopIntegrationActive() bool { |
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.
👍🏻
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 refactored other places in the code that also did this validation
@@ -124,7 +146,7 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options | |||
return err | |||
}) | |||
|
|||
if options.Start.Watch { | |||
if options.Start.Watch && !options.Start.NavigationMenu { |
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.
IIUC --watch
would then not start watch mode until menu is disabled. Why ?
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right. yet more spaghetti code to our already confusing "architecture" 😅
pkg/compose/watch.go
Outdated
if !watching { | ||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I personally preferred "enabled", but that's just me
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, even the navigation menu message switches between watch enabled/disabled
4ed8a30
to
fc8b195
Compare
Signed-off-by: Joana Hrotko <joana.hrotko@docker.com>
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
up
run:OR set environment var
COMPOSE_MENU=[false|true]
OR use Docker Desktop experimental features and enable/disable -
Compose: TUI nav bar
Screen.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/NAME
Related 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