go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/README.md (about)

     1  # LUCI Test Single UI
     2  LUCI Test Single UI is the web UI for all LUCI Test systems.
     3  
     4  ## Local Development Workflows
     5  ### Prerequisites
     6  You need to run the following commands to setup the environment.
     7  ```sh
     8  # Activate the infra env (via the infra.git checkout):
     9  cd /path/to/infra/checkout
    10  eval infra/go/env.py
    11  
    12  # Install the dependencies.
    13  cd /path/to/this/directory
    14  npm ci
    15  ```
    16  
    17  ### Start a local AppEngine server
    18  TODO: add instructions
    19  
    20  For the simple case of testing an rpc using `/rpcexplorer`:
    21  ```
    22  go run main.go -cloud-project luci-milo-dev -auth-service-host chrome-infra-auth-dev.appspot.com
    23  ```
    24  
    25  ### Start a local UI server
    26  To start a [Vite](https://vitejs.dev) local dev server, run
    27  ```
    28  npm run dev
    29  ```
    30  
    31  The local dev server only serves the SPA assets. It sends pRPC and HTTP REST
    32  requests to staging servers (typically hosted on GCP). Check
    33  [.env.development](.env.development) for instructions to configure the target
    34  servers and other local development settings.
    35  
    36  
    37  #### Login on a local UI server
    38  Currently, there's no easy way to perform a login flow on a local UI server. To
    39  test the page with a logged in session, you can
    40  
    41   - deploy to a personal staging server with the following command, or
    42  
    43     1. `npm run build && yes | gae.py upload -p ../ -A luci-milo-dev default`
    44  
    45   - use an auth state obtained from staging or prod environment with the
    46     following steps.
    47  
    48     1. open `` `https://${singleUiHost}/ui/search` `` in a browser tab, then
    49     2. perform a login flow, then
    50     3. run `copy(JSON.stringify(__STORE.authState.value))` in the browser devtool
    51     console to copy the auth state, then
    52     4. paste the output to [auth_state.local.json](auth_state.local.json), then
    53     5. visit any pages under http://localhost:8080/ui/.
    54  
    55     note that the auth state obtained from a dev environment cannot be used to
    56        query prod services and vice versa.
    57  
    58  ### Deploy a demo to GAE dev instance
    59  This deploys your local code to GAE as a dev instance that uses real auth
    60  and can be accessed by other people.
    61  
    62  ```
    63  cd ui
    64  npm run build
    65  gae.py upload -p ../ -A luci-milo-dev default
    66  ```
    67  
    68  ### Add a new npm package
    69  You can use [npm install](https://docs.npmjs.com/cli/v8/commands/npm-install) to
    70  add a new npm package.
    71  
    72  LUCI Test Single UI uses a private npm registry (defined in [.npmrc](.npmrc)).
    73  By default, it rejects packages that are less than 7 days old (with an
    74  HTTP 451 Unknown error).
    75  
    76  You can avoid this issue by temporarily switching to the public npm registry
    77  with the following steps.
    78  
    79   1. comment out the registry setting in [.npmrc](.npmrc), then
    80   2. install the package, then
    81   3. replace the registry in all the URLs in the "resolved" field in
    82   [package-lock.json](package-lock.json) with the private registry, then
    83   4. uncomment the registry setting in [.npmrc](.npmrc), then
    84   5. wait 7 days before submitting your CL.
    85  
    86  ### Others
    87  Check the [Makefile](Makefile), the [parent Makefile](../Makefile), and the
    88  `"scripts"` section in [package.json](package.json) for more available commands.
    89  
    90  ## Design Decisions
    91  ### Service Workers
    92  To improve the loading time, the service workers are added to
    93   * redirect users from the old URLs to the new URLs (e.g. from
    94     `` `/b/${build_id}` `` to `` `/ui/b/${build_id}` ``) without hitting the
    95     server, and
    96   * cache and serve static assets, including the entry file (index.html), and
    97   * prefetch resources if the URL matches certain pattern.
    98  
    99  It's very hard (if not impossible) to achieve the results above without service
   100  workers because most page visits hit a dynamic (e.g.
   101  `` `/p/${project}/...` ``), and possibly constantly changing (e.g.
   102  `` `/b/${build_id}` ``) URL path, which means cache layers that rely on HTTP
   103  cache headers will not be very effective.
   104  
   105  ### [Src Directory](./src) Structure
   106  #### Goals
   107  The directory structure is designed to achieve the following goals:
   108   * Fit to host all LUCI Single UI projects.
   109   * Discovering/locating existing modules should be straightforward.
   110   * Deciding where a new module should be placed should not be confusing.
   111   * Managing (first party) modules dependencies should be straightforward, which
   112     requires
   113     * making the dependency relationship between modules more obvious, and
   114     * making it harder to accidentally introducing circular dependencies.
   115   * Support different levels of encapsulation, which includes the abilities to
   116     * limit the surface of a module, and
   117     * enforce module-level invariants.
   118  
   119  #### Rules
   120  To achieve those goals, the [./src](./src) directory are generally structured
   121  with the following rules:
   122   * Modules are grouped into packages. Packages are all located at the top level
   123     directory in the ./src directory. It can be one of the followings:
   124     * A business domain package (e.g. [@/build](./src/build),
   125       [@/bisection](./src/bisection)).
   126       * The contained modules are specific to the business domain.
   127       * The business domain typically matches a top-level navigation/feature
   128         area.
   129       * A domain package may import from another domain package
   130         * This is supported so natural dependencies between domains can be
   131           expressed (e.g. builds depends on test results).
   132         * The usage should still be limited. Consider lifting the shared module
   133           to the @/common package.
   134         * Circular dependencies between domain packages must be avoided.
   135       * Grouping modules by business domains makes enforcing encapsulation/
   136         isolation easier.
   137       * Placing modules under a package named after their domain helps
   138         discovering/locating modules as the project grows larger.
   139     * The [@/common](./src/common) package.
   140       * The contained modules can have business/application logic.
   141       * The contained modules must not make assumptions on the business domains
   142         it's used in.
   143       * The contained modules should be reusable across business domains.
   144       * Must not import from domain packages.
   145       * This helps capturing modules that cross domains.
   146       * The name gives a clear signal that the modules should stay reusable.
   147     * The [@/generic_libs](./src/generic_libs) package.
   148       * The contained modules must not contain any business logic.
   149       * The contained modules should be highly generic and reusable (akin to a
   150         published module).
   151       * The contained modules (excluding unit tests) must not depend on any first
   152         party module outside of [@/generic_libs](./src/generic_libs).
   153       * Comparing to @/common, [@/generic_libs](./src/generic_libs) must not
   154         contain business/application logic.
   155       * The name gives a clear signal that the modules should stay generic.
   156       * Separating from @/common makes it harder to accidentally
   157         add business logic to a generic module.
   158     * The [@/core](./src/core) package.
   159       * The contained modules are for core functionality such as login or
   160         the landing page.
   161     * The [@/testing_tools](./src/testing_tools) package.
   162       * Contains utilities for writing unit tests.
   163       * Can import from other packages or be imported to other packages.
   164       * Must only be used in unit tests.
   165       * Other directories may have their own `./testing_tools` subdirectory to
   166         contain testing tools specific to those domains.
   167       * It helps separating test utilities from production code.
   168   * Modules are usually further divided into groups by their functional category
   169     (e.g. `./components/*`, `./hooks/*`, `./tools/*`).
   170     * The purpose is to make locating/discovering existing modules easier as the
   171       module list grows larger.
   172     * The divide is purely aesthetical.
   173       * There's no logical boundary between those groups and the division does
   174         not signal or enforce encapsulation.
   175       * It's perfectly fine to have "circular imports" between groups since they
   176         are merely aesthetical groups.
   177       * Circular dependencies between the actual underlying modules should still
   178         be avoided.
   179     * This rule is enforced loosely.
   180       * Modules belong to multiple functional categories can simply pick a group
   181         with the best fit (e.g. a module that exports React components, hooks and
   182         utility functions may simply be placed under `./components/`).
   183       * Modules that don't fit any of the categories may be placed directly in
   184         the parent directory or in a catch all group (e.g. `./tools/`). This
   185         should be used sparingly.
   186   * Modules may declare entry files (e.g. `index.ts`) that reexport symbols.
   187     Symbols reexported by the entry file are considered the public surface of the
   188     module, while other symbols are considered internal to the module (i.e. no
   189     deep imports when there's an entry file).
   190     * This helps reducing the public surface of a module. Makes it easier to
   191       implement encapsulation and enforce invariants.
   192   * Modules can themselves have different internal structures to implement
   193     different layers of encapsulation.
   194  
   195  Note: At the moment (2023-09-14), some packages are in an inconsistent state.
   196  Some modules should be moved to other packages. Notable items include but not
   197  limited to
   198   * Some modules in @/common should be moved to business domain packages.
   199   * [@/bisection](./src/bisection) and other recently merged in projects should
   200     have common modules lifted to @/common.
   201  
   202  #### Graph illustration of the package relationships:
   203  ```ascii
   204  @/core                    ─┬─> @/build                        ─┬─> @/common           ─┬─> @/generic_libs
   205    ├─■ ./pages             │     ├─■ ./pages                   │     ├─■ ./components  │     ├─■ ./components
   206    ├─■ ...other groups...  │     ├─■ ./components              │     ├─■ ./hooks       │     ├─■ ./hooks
   207    ├─■ ...entry files...   │     ├─■ ./hooks                   │     ├─■ ./tools       │     ├─■ ./tools
   208    └─■ ...                 │     ├─■ ./tools                   │     └─■ ...           │     └─■ ...
   209                            │     └─■ ...                       │                       │
   210                            │                                   │                       ├─> ...third party libs...
   211                            ├─> @/bisection                    ─┤                       │
   212                            │     ├─■ ./pages                   │                       │
   213                            │     ├─■ ./components              │                       │
   214                            │     ├─■ ./hooks                   │                       │
   215                            │     ├─■ ./tools                   │                       │
   216                            │     └─■ ...                       │                       │
   217                            │                                   │                       │
   218                            ├─> @/analysis                     ─┤                       │
   219                            │     └─■ ...                       │                       │
   220                            │                                   │                       │
   221                            ├─> @/...other business domains... ─┤                       │
   222                            │     └─■ ...                       │                       │
   223                            │                                   │                       │
   224                            └─> ────────────────────────────────┴─> ────────────────────┘
   225  
   226  A ─> B: A depends on B.
   227  A ─■ B: A contains B.
   228  ```