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

Add a modern web UI based on React, MaterialUI and Vite #2405

Merged
merged 34 commits into from
Oct 10, 2023

Conversation

andrewbaldwin44
Copy link
Collaborator

@andrewbaldwin44 andrewbaldwin44 commented Sep 22, 2023

Changes Proposed

  • Add a new boolean argument modern_ui (--modern-ui, LOCUST_MODERN_UI). When true, will use the new frontend
  • Have UIExtraArgOptions returned as a dict. This is needed as otherwise the NamedTuple is returned as an Array when JSON stringified. This is tested and working in the old web ui
  • Add React frontend using Typescript, Redux Toolkit, and Material UI. The UI supports all existing features from the old UI
  • Add python tests for the new Web UI
  • Add an example of how it can be possible to extend upon the new modern UI
  • Add documentation for developing the dashboard

Fixes #2396

Demo

Light and dark mode:
image
image

Advanced options and custom arguments:
image

About Modal:
image

Running Test:
image
image
image
image
image
image
image

Exceptions:
image

Second Test Markers:
image

Class Picker:
image

Custom Shape:
image

Custom Shape with Class Picker:
image

Multiple Hosts:
image
image

Full Requests Statistics History:
image

With Workers:
image
image
image

With examples/extend_modern_web_ui.py
image
image

@andrewbaldwin44
Copy link
Collaborator Author

@heyman @KasimAhmic I know both of you suggested using Vite for this project, would either of you be able to assist in changing the Webpack code to Vite? As I mentioned, I am not at all familiar with the tool. I tried with the following config:

import reactSwcPlugin from '@vitejs/plugin-react-swc';
import { defineConfig, splitVendorChunkPlugin } from 'vite';
import checkerPlugin from 'vite-plugin-checker';
import tsconfigPaths from 'vite-tsconfig-paths';

// https://vitejs.dev/config/
export default defineConfig({
  root: 'src',
  publicDir: '../public',
  plugins: [
    tsconfigPaths(),
    reactSwcPlugin(),
    splitVendorChunkPlugin(),
    checkerPlugin({
      typescript: true,
      eslint: {
        lintCommand: 'eslint --ext .ts,.tsx',
      },
    }),
  ],
  build: {
    outDir: '../dist',
    emptyOutDir: true,
  },
  server: {
    port: 4000,
  },
});

This unfortunately would not load any images properly (I even tried directly importing, such as they suggest here).

I additionally couldn't seem to find any replacement in Vite for the Webpack watch command. Since the locust instance will read from the dist, we need to have the build emitted for development while running a locust instance.

Speaking of development - I propose in Webpack to use two different html files, one for developing using the Webpack dev server (it uses test data for the template arguments so we do not need to have a Locust server running) and one for production / developing with a Locust instance. Could the same be easily achieved in Vite?

@KasimAhmic
Copy link
Contributor

Checked out your code and got it working with the config below:

import reactSwcPlugin from '@vitejs/plugin-react-swc';
import { defineConfig, splitVendorChunkPlugin } from 'vite';
import checkerPlugin from 'vite-plugin-checker';
import tsconfigPaths from 'vite-tsconfig-paths';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    reactSwcPlugin(),
    tsconfigPaths(),
    splitVendorChunkPlugin(),
    checkerPlugin({
      typescript: true,
      eslint: {
        lintCommand: 'eslint --ext .ts,.tsx',
      },
    }),
  ],
  build: {
    outDir: '../dist',
    emptyOutDir: true,
  },
  server: {
    port: 4000,
    open: './dev.html'
  },
});

Main changes are:

  1. Removed the root and publicDir options
  2. Added the open option pointing to the dev.html file

In addition to the config, I also moved the index.html and dev.html files from the src directory to the dashboard directory as per Vite's requirements. Similarly, you will need to add the following line in each HTML file.

<script type="module" src="/src/index.tsx"></script>

Because we specified the dev file in the server configuration, this should cover your use case of having two HTML files as I believe Vite will still use index.html on production builds.

Regarding watch mode, Vite does have it built in. You can use it like so: vite build --watch

And lastly, with regard to images, when loading images from the public directory, you would reference them as if the images are available at the root of the site like so:

- <img height='52' src='/assets/logo.png' width='52' />
+ <img height='52' src='/logo.png' width='52' />
image

@andrewbaldwin44
Copy link
Collaborator Author

@cyberw There is one other thing that I believe is missing here. I believe for the UI to be properly bundled when pip install locust is ran, we need to have some additional configuration in the pyproject.toml. The output of the project, dashboard/dist is being gitignored, meaning it will not exist when others install the project. Do you know of some way that we could have setup tools build the dist on install? Or do we need to store the dist in the project? It seems like when we were using SASS the bundled CSS was being uploaded to github right? I am unfortunately not so familiar with this side of the Python workflow

@andrewbaldwin44
Copy link
Collaborator Author

Hey @KasimAhmic thanks so much for the assist! Looks like the --watch flag works great and I was able to configure the HTML file for dev / prod using the following:

import reactSwcPlugin from '@vitejs/plugin-react-swc';
import { defineConfig, splitVendorChunkPlugin } from 'vite';
import checkerPlugin from 'vite-plugin-checker';
import tsconfigPaths from 'vite-tsconfig-paths';

const PRODUCTION = process.env.PRODUCTION === 'true';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    reactSwcPlugin(),
    tsconfigPaths(),
    splitVendorChunkPlugin(),
    checkerPlugin({
      typescript: true,
      eslint: {
        lintCommand: 'eslint ./src/**/*.{ts,tsx}',
      },
    }),
  ],
  server: {
    port: 4000,
    open: PRODUCTION ? './index.html' : './dev.html'
  },
});

The images do work when running the dev server, however there seems to be an issue with how the assets are placed in the dist. Try running PRODUCTION=true yarn vite build --watch in one terminal and then run a locust instance in another: locust -f locust.py --modern-ui

@KasimAhmic
Copy link
Contributor

@andrewbaldwin44 Couple things

  1. I ran into the same issue with the production build actually. This feels like it might be a Flask configuration issue though, I saw something about Flask only checking a static directory for static assets. Is it possible to change this?

  2. You can get rid of this line: const PRODUCTION = process.env.PRODUCTION === 'true'; and replace the value passed to defineConfig with a function that takes a configuration object as an argument. For example:

import reactSwcPlugin from '@vitejs/plugin-react-swc';
import { defineConfig, splitVendorChunkPlugin } from 'vite';
import checkerPlugin from 'vite-plugin-checker';
import tsconfigPaths from 'vite-tsconfig-paths';

// https://vitejs.dev/config/
export default defineConfig((config) => ({
  plugins: [
    reactSwcPlugin(),
    tsconfigPaths(),
    splitVendorChunkPlugin(),
    checkerPlugin({
      typescript: true,
      eslint: {
        lintCommand: 'eslint ./src/**/*.{ts,tsx}',
      },
    }),
  ],
  server: {
    port: 4000,
    open: config.mode === 'production' ? './index.html' : './dev.html'
  },
}));
  1. The server configuration is only used in development mode so I think you should be able to delete the conditional open configuration and just leave it as open: 'dev.html'. Could be wrong, never did something like this before.

@cyberw
Copy link
Collaborator

cyberw commented Sep 23, 2023

@cyberw There is one other thing that I believe is missing here. I believe for the UI to be properly bundled when pip install locust is ran, we need to have some additional configuration in the pyproject.toml. The output of the project, dashboard/dist is being gitignored, meaning it will not exist when others install the project. Do you know of some way that we could have setup tools build the dist on install? Or do we need to store the dist in the project? It seems like when we were using SASS the bundled CSS was being uploaded to github right? I am unfortunately not so familiar with this side of the Python workflow

I dont know the best way to do this off hand, and I'm kinda busy atm. Let me know if you're really stuck on it and I'll have a look.

@cyberw
Copy link
Collaborator

cyberw commented Sep 23, 2023

@andrewbaldwin44 For adding the files, you should just need to include them in MANIFEST.in, like this:

recursive-include dashboard/dist *

@cyberw
Copy link
Collaborator

cyberw commented Sep 23, 2023

The failures page is missing the "type" column? (but lets change the column name to "Message" or "Error message", while we are at it)
image
old school:
image

@andrewbaldwin44
Copy link
Collaborator Author

Thanks everyone! @cyberw updated now with the missing "Message" column and the missing line in the MANIFEST.in
image

Thanks @KasimAhmic you were correct, I have Flask configured to server assets from /assets and Webpack was copying the assets to dist/assets but Vite was not. The dashboard is now using Vite for compiling 👍

@cyberw
Copy link
Collaborator

cyberw commented Sep 25, 2023

Cool stuff! Can you add a basic test case in test_web.py as well? And maybe have a look at the weird html escaping going on in your screenshot of the failures tab :)

@andrewbaldwin44
Copy link
Collaborator Author

@cyberw Sure, I can add a test to ensure the correct behavior is happening with the --modern-ui flag. For testing the new web ui, we should do it using React Testing Library. That part will take time, do you prefer me to do it in this feature, or in a future ticket?

@cyberw
Copy link
Collaborator

cyberw commented Sep 25, 2023

You can add react testing in a later PR, that is fine. For test_web.py, a basic test that runs with --modern-ui, loads the start page without error and can start a test run is enough.

@cyberw cyberw changed the title [Feature/2396] Proposal for Re-Writing the Web UI Add a modern React based web UI Sep 25, 2023
@cyberw cyberw changed the title Add a modern React based web UI Add a modern web UI based on React, MaterialUI and Vite Sep 25, 2023
@andrewbaldwin44
Copy link
Collaborator Author

@cyberw Updated now to support run_time from CLI as default arg (spawn_rate was already supported in the new UI)
image

@cyberw
Copy link
Collaborator

cyberw commented Sep 25, 2023

From a code perspective this looks really good, apart from the flake8 and black fails :) (I really should make them pre-commit checks)

I think we should mention the availability of the modern UI explicitly in the manual. I’m thinking maybe as a note at the end of the web ui section of quickstart? And make it clear that it is experimental, and breaking changes may happen. Mark it experimental in the command line description too.

@andrewbaldwin44
Copy link
Collaborator Author

Apologies for the failing pipeline, I committed the new tests a bit too quickly! The pipeline is passing now and I have added a line to the quick start guide mentioning the new web UI as well as a warning that this feature is experimental.

@cyberw
Copy link
Collaborator

cyberw commented Sep 26, 2023

Awesome stuff. I'll try it out in the coming days.

docs/developing-locust.rst Outdated Show resolved Hide resolved
@cyberw
Copy link
Collaborator

cyberw commented Sep 26, 2023

Couple things:

  • If I refresh the page, everything disappears. The view is not updated until a new request comes in (I think, or maybe it is some other delay, also the "new" button is shown instead of edit/stop/reset)
  • The --users parameter is not prefilled correctly (it is "1" no matter what value I set on command line)
  • If I use --autostart the UI still shows the "new" button (when it should show edit/stop/reset) (this is probably just another version of the "refresh" problem)

@cyberw
Copy link
Collaborator

cyberw commented Sep 26, 2023

Also, the "download report" page still looks like its the old version:
image

@andrewbaldwin44
Copy link
Collaborator Author

andrewbaldwin44 commented Oct 6, 2023

Hey guys, I have just pushed an update that should resolve all the comments posted above. I will still need some more time for getting the API logic working with RTK, I will need to look into it more next week

locust/test/test_web.py Outdated Show resolved Hide resolved
@cyberw
Copy link
Collaborator

cyberw commented Oct 9, 2023

tests on python 3.8 are flaky for some reason. you can ignore the build failure on 3.8 for now.

@andrewbaldwin44
Copy link
Collaborator Author

Sorry for the delay everyone, all the comments are now resolved and I have replaced the API logic with RTK's createApi. Thanks for the feedback! :)

@cyberw
Copy link
Collaborator

cyberw commented Oct 10, 2023

Nice! Can you update the pr description? I think the following is no longer relevant and maybe there are other things to add.

Modify the response from /stats/reset to be JSON. I propose this change to be consistent with all other responses from the API (without this change we may see console errors in the new UI as it expects responses to always be JSON)

@cyberw
Copy link
Collaborator

cyberw commented Oct 10, 2023

Great stuff. I'm going to merge this tomorrow if nobody objects. Further improvements are of course very welcome later on. Especially a few UI test cases would be nice.

Edit: Why wait until tomorrow?

@andrewbaldwin44
Copy link
Collaborator Author

@cyberw Yes once this is merged I can open a PR for updating the HTML report and then a third PR to introduce a suite of tests using React testing library. From there I am happy to support with the community requested features, such as boolean custom arguments and having a tab for viewing the logs :)

@cyberw cyberw merged commit 704a015 into locustio:master Oct 10, 2023
14 checks passed
@cyberw
Copy link
Collaborator

cyberw commented Nov 10, 2023

@mquinnfd Does your offer to implement poetry to build the frontend still stand? If so, I'm more interested now :)

I've heard more good stuff about it (and the bad stuff I heard/inferred was maybe a little misguided).

The one thing I want to make sure is that we dont lose the ability to do an editable install (-e) that can be used even outside the directory/venv (maybe PDM is better than poetry for that?)

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.

Proposal for Re-Writing the Web UI
4 participants