Skip to content

UI: Overhaul lakeFS look & feel #9022

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open

UI: Overhaul lakeFS look & feel #9022

wants to merge 7 commits into from

Conversation

ozkatz
Copy link
Collaborator

@ozkatz ozkatz commented May 6, 2025

Sorry for the big PR that does a lot of things. Here's a quick recap of what's included:

  1. Split global.css into smaller, easier to maintain files (this alone is responsible for a lot of the diff)
  2. Update some bootstrap styles to look a bit more up-to-date with current web design 🫥
  3. Improve empty state for new repos with no objects, and for the "uncommitted changes" tab when no modifications have been made
  4. Improve usability of upload modal (allowing to rename files...)

Admittedly, some of the CSS changes also come with modifications for the structure of the page which increases bloat on the diff, but they are almost all cosmetic.

@ozkatz ozkatz requested review from nopcoder, Annaseli and Ben-El May 6, 2025 00:59
@ozkatz ozkatz added include-changelog PR description should be included in next release changelog minor-change Used for PRs that don't require issue attached labels May 6, 2025
Copy link

github-actions bot commented May 6, 2025

E2E Test Results - DynamoDB Local - Local Block Adapter

9 passed, 4 failed, 1 skipped

Test failures:
  Read Only Repository › Read only indicator shown on repository page and upload button is disabled: common/readOnlyRepository.spec.ts

readOnlyRepository.spec.ts:24:9 Read only indicator shown on repository page and upload button is disabled

[common] › common/readOnlyRepository.spec.ts:24:9 › Read Only Repository › Read only indicator shown on repository page and upload button is disabled

Error: expect.toBeDisabled: Error: strict mode violation: locator('text=Upload Object') resolved to 2 elements:
    1) <button disabled type="button" title="Upload objects" cl…>…</button> aka getByRole('button', { name: 'Upload Object' })
    2) <div class="card-title h5">Upload Objects</div> aka getByText('Upload Objects')

Call log:
  - expect.toBeDisabled with timeout 5000ms
  - waiting for locator('text=Upload Object')


  28 |         const repositoryPage = new RepositoriesPage(page);
  29 |         await expect(repositoryPage.readOnlyIndicatorLocator).toBeVisible();
> 30 |         await expect(repositoryPage.uploadButtonLocator).toBeDisabled();
     |                                                          ^
  31 |     });
  32 | })

    at /home/runner/work/lakeFS/lakeFS/webui/test/e2e/common/readOnlyRepository.spec.ts:30:58

attachment #1: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/common-readOnlyRepository-Read-Only-Repository-d756e-n-repository-page-and-upload-button-is-disabled-common/trace.zip
Usage:

    npx playwright show-trace test-results/common-readOnlyRepository-Read-Only-Repository-d756e-n-repository-page-and-upload-button-is-disabled-common/trace.zip

────────────────────────────────────────────────────────────────────────────────────────────────

  Repositories Page › search input: common/repositoriesPage.spec.ts
repositoriesPage.spec.ts:11:9 search input

  [common] › common/repositoriesPage.spec.ts:11:9 › Repositories Page › search input ───────────────
Error: Timed out 5000ms waiting for expect(locator).toBeVisible()

Locator: getByPlaceholder('Find a repository...')
Expected: visible
Received: hidden
Call log:
  - expect.toBeVisible with timeout 5000ms
  - waiting for getByPlaceholder('Find a repository...')


  12 |         const repositoriesPage = new RepositoriesPage(page);
  13 |         await repositoriesPage.goto();
> 14 |         await expect(repositoriesPage.searchInputLocator).toBeVisible();
     |                                                           ^
  15 |     });
  16 | });
  17 |

    at /home/runner/work/lakeFS/lakeFS/webui/test/e2e/common/repositoriesPage.spec.ts:14:59

attachment #1: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/common-repositoriesPage-Repositories-Page-search-input-common/trace.zip
Usage:

    npx playwright show-trace test-results/common-repositoriesPage-Repositories-Page-search-input-common/trace.zip

────────────────────────────────────────────────────────────────────────────────────────────────

  Upload File › Create repo, Upload file check path: common/uploadFile.spec.ts
uploadFile.spec.ts:14:7 Create repo, Upload file check path

  [common] › common/uploadFile.spec.ts:14:7 › Upload File › Create repo, Upload file check path ────
Test timeout of 30000ms exceeded.

Error: locator.click: Test timeout of 30000ms exceeded.
Call log:
  - waiting for getByText('Drag \'n\' drop files or')


   at poms/repositoryPage.ts:136

  134 |   async uploadObject(filePath: string): Promise<void> {
  135 | 	await this.page.getByRole("button", { name: "Upload Object" }).click();
> 136 | 	await this.page.getByText("Drag 'n' drop files or").click();
      | 	                                                    ^
  137 | 	const fileInput = await this.page.locator('input[type="file"]');
  138 | 	await fileInput.setInputFiles(filePath);
  139 |   }

    at RepositoryPage.uploadObject (/home/runner/work/lakeFS/lakeFS/webui/test/e2e/poms/repositoryPage.ts:136:54)
    at /home/runner/work/lakeFS/lakeFS/webui/test/e2e/common/uploadFile.spec.ts:29:4

attachment #1: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/common-uploadFile-Upload-File-Create-repo-Upload-file-check-path-common/trace.zip
Usage:

    npx playwright show-trace test-results/common-uploadFile-Upload-File-Create-repo-Upload-file-check-path-common/trace.zip

────────────────────────────────────────────────────────────────────────────────────────────────

  Object Viewer - Parquet File › view parquet object: common/viewParquetObject.spec.ts
viewParquetObject.spec.ts:18:9 view parquet object

  [common] › common/viewParquetObject.spec.ts:18:9 › Object Viewer - Parquet File › view parquet object 
Error: expect.not.toBeVisible: Error: strict mode violation: getByText('Loading...') resolved to 2 elements:
    1) <span class="visually-hidden">Loading...</span> aka getByRole('status').getByText('Loading...')
    2) <div class="loading-text text-center text-muted">Loading...</div> aka getByText('Loading...').nth(1)

Call log:
  - expect.not.toBeVisible with timeout 5000ms
  - waiting for getByText('Loading...')


  23 |         const repositoryPage = new RepositoryPage(page);
  24 |         await repositoryPage.clickObject(PARQUET_OBJECT_NAME);
> 25 |         await expect(page.getByText("Loading...")).not.toBeVisible();
     |                                                        ^
  26 |     });
  27 |
  28 |     test("view parquet object w/ logout and login", async ({page}) => {

    at /home/runner/work/lakeFS/lakeFS/webui/test/e2e/common/viewParquetObject.spec.ts:25:56

attachment #1: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/common-viewParquetObject-Object-Viewer---Parquet-File-view-parquet-object-common/trace.zip
Usage:

    npx playwright show-trace test-results/common-viewParquetObject-Object-Viewer---Parquet-File-view-parquet-object-common/trace.zip

────────────────────────────────────────────────────────────────────────────────────────────────

This test report was produced by the test-summary action.  Made with ❤️ in Cambridge.

Copy link

github-actions bot commented May 6, 2025

E2E Test Results - Quickstart

7 passed, 1 failed, 4 skipped

Test failures:
  Quickstart › view and query parquet object: common/quickstart.spec.ts

quickstart.spec.ts:33:9 view and query parquet object

[quickstart] › common/quickstart.spec.ts:33:9 › Quickstart › view and query parquet object ───────

Error: expect.not.toBeVisible: Error: strict mode violation: getByText('Loading...') resolved to 2 elements:
    1) <span class="visually-hidden">Loading...</span> aka getByRole('status').getByText('Loading...')
    2) <div class="loading-text text-center text-muted">Loading...</div> aka getByText('Loading...').nth(1)

Call log:
  - expect.not.toBeVisible with timeout 5000ms
  - waiting for getByText('Loading...')


  38 |         const repositoryPage = new RepositoryPage(page);
  39 |         await repositoryPage.clickObject(PARQUET_OBJECT_NAME);
> 40 |         await expect(page.getByText("Loading...")).not.toBeVisible();
     |                                                        ^
  41 |
  42 |         const objectViewerPage = new ObjectViewerPage(page);
  43 |         await objectViewerPage.enterQuery(SELECT_QUERY);

    at /home/runner/work/lakeFS/lakeFS/webui/test/e2e/common/quickstart.spec.ts:40:56

attachment #1: trace (application/zip) ─────────────────────────────────────────────────────────
test-results/common-quickstart-Quickstart-view-and-query-parquet-object-quickstart/trace.zip
Usage:

    npx playwright show-trace test-results/common-quickstart-Quickstart-view-and-query-parquet-object-quickstart/trace.zip

────────────────────────────────────────────────────────────────────────────────────────────────

This test report was produced by the test-summary action.  Made with ❤️ in Cambridge.

Copy link
Contributor

@nopcoder nopcoder left a comment

Choose a reason for hiding this comment

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

Liked the new UI - will let the experts review and give feedback.
Need to fix the UI automation - let me know if help is needed there.

Copy link
Contributor

@Ben-El Ben-El left a comment

Choose a reason for hiding this comment

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

First of all - Great work overall !
This PR brings valuable UX and architectural improvements, and code readability has generally improved compared to the previous structure.
I personally appreciate the shift from a single path to per-file destination :)

Please note that this PR is quite large, and while the changes are valuable and well structured, it makes the review process significantly harder and more time consuming.

In the future, it would be helpful to split such large changes into smaller, focused PRs (e.g. separating UI refactors from logic changes). That would make it easier to review thoroughly and provide better feedback faster.


Here are a few general suggestions for improvement:

  1. The objects.jsx file is quite large (1000+ lines). Breaking it up could help with testability and maintainability.
    Highly recommend splitting this component into smaller subcomponents.

  2. Since this PR introduces behavioral changes (e.g., per-file destination paths, editable paths, cancellation handling), it’s important to ensure that there will be at least some unit tests / add playwright tests for it.

  3. OPTIONAL - consider wrapping the AbortController logic into a custom hook if similar cancellation patterns are planned elsewhere in the project. For now, it's fine inline.

<div className="d-flex align-items-center mt-4">
<span className="learn-more">Already working with lakeFS?</span>
<GettingStartedCreateRepoButton
style={{ padding: 0, width: "auto", marginLeft: "8px", display: "inline-block" }}
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggest adding a class to a css file and using it here

Comment on lines 475 to 477
useEffect(() => {
setCurrentPath(path)
setOverallPath(path || "")
}, [path])
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider if this effect is needed at all, or if the default value can be handled outside the effect

@idanovo
Copy link
Contributor

idanovo commented May 11, 2025

@ozkatz while trying to play around with this branch, I found a bug- the Upload Object button on the Uncommitted Changes page redirects me back to the objects page of the repository.
I'm also not sure why we need this button on the uncommitted Changes page, It didn't exist before, AFAIK

@nopcoder
Copy link
Contributor

Addressing the e2e test on a different PR based on this one - #9098.

@nopcoder
Copy link
Contributor

@ozkatz merged my changes for the UI tests, should be green now.

@ozkatz ozkatz force-pushed the feat/ui-overhaul branch from 9b26716 to d9bdd97 Compare May 26, 2025 22:33
@ozkatz
Copy link
Collaborator Author

ozkatz commented May 26, 2025

@ozkatz while trying to play around with this branch, I found a bug- the Upload Object button on the Uncommitted Changes page redirects me back to the objects page of the repository. I'm also not sure why we need this button on the uncommitted Changes page, It didn't exist before, AFAIK

Agreed, I did want to have a concrete call to action for empty states, which we didn't have before. Good catch - I fixed the bug.

@ozkatz ozkatz requested review from Ben-El and nopcoder May 26, 2025 23:05
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're modifying globals.css in this PR, I wanted to share something I learned from @itaigilo: it's best to minimize the use of global CSS and inline styles, and instead rely on built-in Bootstrap classes as much as possible. These classes have already been tested across various screens and scenarios, and they follow established best practices for styling.

From what I understood from Itai, the long-term goal is to remove most of the custom styles in globals.css and replace them with Bootstrap classes. However, I noticed that this PR introduces several new CSS files in the styles directory.

@itaigilo @oz What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

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

The purpose of a globals.css is to provide general styling for the app.
So in this sense, the AI took a good step in this direction, leaving here only general elements style (such as h* or body styling, and color-schema), and moving the rest to the "Areas" css files.
These files are tricky from my POV, but I've commented on the auth.css.

Comment on lines 50 to 52
const isAbortedError = (error, controller) => {
return (error instanceof DOMException && error.name === 'AbortError') || controller.signal.aborted;
};
Copy link
Contributor

Choose a reason for hiding this comment

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

When branch protection is on, an error is thrown and this code throw exception while access undefined (controller.signal.aborted) and we get a blank page with exception in the console.

@nopcoder
Copy link
Contributor

The white background needs a fix in the branch protection.

image

Copy link
Contributor

@itaigilo itaigilo left a comment

Choose a reason for hiding this comment

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

This is great effort, in the very right direction.

I totally understand the need for a massive change in the UI, and I think we can benefit a lot from it,
And I also think that using AI for this is probably the only way to do it in 2025.

Having said that, this PR introduces two main things we should consider:

  • It requires proper testing and verification.
  • It introduces a lot of code which is hard to maintain by humans.

I've roughly gone through a chunk of it, finding a lot of things that I wouldn't approve in a PR of a fellow dev.

So if we want to introduce such changes, I think that we should be aware of the consequences.
Do we want the code to be mainly written by AI in the near future?
Are we willing to risk with some fundamental UI bugs resulting from code we don't understand and haven't tested?
Are we ok with massive PRs which are hard to review?

The answer to these questions well might be a "Yes" -
But it should at least be considered.

And again, IMHO -
This is a very motivating step in the very right direction!

dismissible
onClose={onDismiss}
>
<div className="d-flex align-items-center">
Copy link
Contributor

Choose a reason for hiding this comment

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

It's very hard to understand the layout from reading this code.
Better add descriptive classes to such elements.

}

return (
<Alert className={alertClassName} variant="danger">{content}</Alert>
<Alert className={alertClassName} variant="danger">
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a copy of the code above. Should be a component.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed

@@ -421,8 +452,13 @@ export const ToggleSwitch = ({ label, id, defaultChecked, onChange }) => {

export const Warning = (props) =>
<>
<Alert variant="warning">
&#x26A0; { props.children }
<Alert variant="warning" className="shadow-sm">
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be a component.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it is!

</div>
if (emptyStateComponent) {
return emptyStateComponent;
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

The else can/should be removed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

True! removed

@@ -4,6 +4,25 @@ import { createRoot } from 'react-dom/client';
import 'bootstrap/dist/css/bootstrap.css';
import './styles/globals.css';

// Areas
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 expect the Areas css files to be included in the index file of the relevant area.
When all of these are here, it's much easier to leave a lot of unused styles in case of removed / transformed components.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes! will be done in another PR, I think this one introduces enough changes as is :)

Copy link
Contributor

Choose a reason for hiding this comment

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

These styles create a mix:
We mainly rely on bootstrap for the layout and general look-and-feel of the UI (positioning, sizes, margins, etc.),
But here, a decent amount of structural changes was introduces.

This might work, but:

  • It wasn't properly tested on different screen-sizes, browsers, etc (while "plain" bootstrap UI is designed and tested for these).
  • It really hard to maintain by humans.

So taking this approach meaning we're compromising these:

  • Not properly tested on a wide range of scenarios.
  • Hard to maintain by humans.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

True - it was tested on desktop browsers (Chrome, Firefox, Safari). In my defense the existing code is also a mix 🙃. I am aware this is a bad excuse.

Copy link
Contributor

Choose a reason for hiding this comment

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

The purpose of a globals.css is to provide general styling for the app.
So in this sense, the AI took a good step in this direction, leaving here only general elements style (such as h* or body styling, and color-schema), and moving the rest to the "Areas" css files.
These files are tricky from my POV, but I've commented on the auth.css.

padding: 0;
}

.navbar > div {
padding-top: 7px;
h1, h2, h3, h4, h5, h6 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why all h* get the same attributes?

I think this is pretty wrong...

@ozkatz
Copy link
Collaborator Author

ozkatz commented May 27, 2025

The white background needs a fix in the branch protection.

image

Fixed

@ozkatz ozkatz requested review from nopcoder and itaigilo May 27, 2025 22:12
@idanovo
Copy link
Contributor

idanovo commented May 28, 2025

@ozkatz
Upload objects stopped working for some reason
Also Enterprise UI seems to be a bit broken when compiled with the current branch
@Ben-El can you please take a look?

@Ben-El
Copy link
Contributor

Ben-El commented May 28, 2025

@idanovo The upload is fine. Maybe you're not updated with the latest changes locally, just pull changes.
About the ui in general - didn't see many broken ui areas.

Although some things I did noticed (@ozkatz):

  1. The objects listed in the tree, as you can see in the image below, are a bit shifted too far to the right.
    image

  2. Top navbar (was always like that) - its items should be aligned to the center.
    image

  3. Whenever we are NOT in the root objects directory (i.e we're inside a subfolder) - I see some HEAD request is fired to verify whether a README file exists.
    IT BEHAVED LIKE THIS BEFORE - but why? is it by design? seems redundant.
    image

@ozkatz
Copy link
Collaborator Author

ozkatz commented May 28, 2025

@idanovo The upload is fine. Maybe you're not updated with the latest changes locally, just pull changes. About the ui in general - didn't see many broken ui areas.

@Ben-El it was @idanovo who spotted the bug (that did exist) it's fixed now :)

Although some things I did noticed (@ozkatz):

  1. The objects listed in the tree, as you can see in the image below, are a bit shifted too far to the right.
    image

Will correct the margins, I agree.

  1. Top navbar (was always like that) - its items should be aligned to the center.
    image

That's not new in this PR so I won't icrease the scope :)

  1. Whenever we are NOT in the root objects directory (i.e we're inside a subfolder) - I see some HEAD request is fired to verify whether a README file exists.
    IT BEHAVED LIKE THIS BEFORE - but why? is it by design? seems redundant.
    image

This is because the UI will attempt to render a README.md, if exists, in any directory. This is existing behavior, I didn't touch it.

@idanovo
Copy link
Contributor

idanovo commented May 28, 2025

@ozkatz @Ben-El Used latest version

Screen.Recording.2025-05-28.at.17.01.00.mov

@ozkatz
Copy link
Collaborator Author

ozkatz commented May 28, 2025

@ozkatz @Ben-El Used latest version

Screen.Recording.2025-05-28.at.17.01.00.mov

Thanks @idanovo - can you please share whatever appears in your lakeFS log + browser console? I can't seem to reproduce

@idanovo
Copy link
Contributor

idanovo commented May 28, 2025

@ozkatz @Ben-El Used latest version
Screen.Recording.2025-05-28.at.17.01.00.mov

Thanks @idanovo - can you please share whatever appears in your lakeFS log + browser console? I can't seem to reproduce

@ozkatz I figured it out!
It happens only when I try to upload an object to a protected branch.
It seems like @nopcoder already found this issue: #9022 (comment)

@ozkatz
Copy link
Collaborator Author

ozkatz commented May 28, 2025

@ozkatz @Ben-El Used latest version
Screen.Recording.2025-05-28.at.17.01.00.mov

Thanks @idanovo - can you please share whatever appears in your lakeFS log + browser console? I can't seem to reproduce

@ozkatz I figured it out! It happens only when I try to upload an object to a protected branch. It seems like @nopcoder already found this issue: #9022 (comment)

Should be fixed now @idanovo @nopcoder

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
include-changelog PR description should be included in next release changelog minor-change Used for PRs that don't require issue attached
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants