Skip to content
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

Feat: Add progress bar #163

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

Conversation

M7md-Ebrahim
Copy link

@M7md-Ebrahim M7md-Ebrahim commented Jan 10, 2025

This PR introduces an initial implementation of a Progress Bar Component #154

Tasks:

  • Enhance logic and improve the UI.
  • Add ETA.
  • Implement runNonInteractive.
  • Create customizable progress bar styles.
  • Enhance error messages and format them for better readability.
  • Implement a unified interface to allow using both the spinner and progress bar.
  • Add support for different task types (e.g., download, tasks).
  • Write tests.
  • Update documentation with usage examples and setup instructions.

Notes:

  • First commit for the progress bar component, with tasks to be completed in subsequent PRs

@M7md-Ebrahim
Copy link
Author

Hi @pepicrft, can you check if I’m on the right track? This is my first time creating a CLI, and I’m really excited to get your feedback

@pepicrft
Copy link
Contributor

Hi @pepicrft, can you check if I’m on the right track? This is my first time creating a CLI, and I’m really excited to get your feedback

Wow! This is amazing @M7md-Ebrahim. Thanks a lot for taking the lead here. There's no deadline here, so no rush, it's important that you take your time to land a great first contribution and get familiar with all the CLI concepts and ideas.

Copy link
Member

@fortmarek fortmarek left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The animations looks amazing, great job!

image

I have two minor comments on the style of the animation:

  • The labels and the loading blocks should have vertical center alignment
  • The percentage label should be filled with spaces if less than one hundred, so the pipe | doesn't move as the percentage grows.

The implementation overall looks good to me – let's add some tests and documentation and I'd be happy to take another look.

Also, sorry for the PR review having taken a bit longer here. We'll try to do a better job at that, so you get a faster turnaround time 🙂

Comment on lines 4 to 5

class CLIProgressBar {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
class CLIProgressBar {
struct CLIProgressBar {

We prefer using structs whenever possible. Let's use classes only when you need in-place mutability.

@@ -111,6 +111,15 @@ public protocol Noorable {
/// - Parameters:
/// - alerts: The warning messages.
func warning(_ alerts: WarningAlert...)

///
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add some documentation

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let’s leave the docs and tests for the end, after finishing the tasks
What do you think?

func startProgress(total: Int, interval: TimeInterval?, block: @escaping (String, Int) -> Void)
func stop()
}
class DefaultProgressBar: ProgressBar {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
class DefaultProgressBar: ProgressBar {
final class DefaultProgressBar: ProgressBar {

@M7md-Ebrahim
Copy link
Author

@fortmarek Thanks for the great review!

I suggest setting the label or message length to a maximum of 20 characters. If the percentage label is less than 10, add two spaces for alignment, if it’s less than 100, add a single space.

I’ll work on implementing this. Additionally, I’m researching how to calculate the time required for this task to include an ETA.

@fortmarek
Copy link
Member

I suggest setting the label or message length to a maximum of 20 characters. If the percentage label is less than 10, add two spaces for alignment, if it’s less than 100, add a single space.

Yeah, we can trim it with the ... ending if the label is too long.

I’ll work on implementing this. Additionally, I’m researching how to calculate the time required for this task to include an ETA.

This sounds like a nice improvement, but let's add that in a subsequent PR 🙏

Copy link
Contributor

@pepicrft pepicrft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for this @M7md-Ebrahim. Let me know if you need any help to push this to a finish line.

import Rainbow


struct CLIProgressBar {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not prefixing the component names with CLI so I'd stay consistent to that and rename this one to:

Suggested change
struct CLIProgressBar {
struct ProgressBar {

message: "Loading manifests",
successMessage: "Manifests loaded",
errorMessage: "Failed to load manifests",
total: 100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd drop this argument. What are the scenarios where you don't want the progress bar to reach 100%?

successMessage: "Xcode projects and workspace generated",
errorMessage: "Failed to generate Xcode workspace and projects",
total: 200
) { _ in
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task should have an API to control the progress. Something along the lines of:

progressBar(....) { progress in
  try await doSomethingThatTakesTime { percentage in // e.g. downloading a file
     progress(percentage)
  }
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to separate the calculation of the progress bar task, can you explain more? i found this method in the docs

Comment on lines +3 to +7
protocol ProgressBar {
func startProgress(total: Int, interval: TimeInterval?, block: @escaping (String, Int) -> Void)
func stop()
}
final class DefaultProgressBar: ProgressBar {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd merge all of this into the ProgressBar component to minimise the number of files making the codebase easier to navigate. If any logic from the component is reusable in the future, we can then make the decision to extract it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, but I think it would be beneficial, as the progress bar still needs many improvements.

Comment on lines +56 to +60
progressBar.startProgress(total: total, interval: 0.05) { progressBarState, percentage in
bar = progressBarState
progressPercentage = percentage
self.render(lastMessage, bar, progressPercentage)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in another comment, it shouldn't be the controller the one that controls the progress, but the caller that uses the component to run an asynchronous action.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants