Skip to content

Commit bb27d3e

Browse files
authored
SOLR-14414: Introduce new UI (SIP-7) (#2605)
* Add basic Compose integration example for webapp This commit creates a new module in Solr that sets up a frontend written with Compose and targeting browser (WASM) and desktop (JVM). The webapp is modified so that it opens the WASM Compose app when accessing /solr/compose. IMPORTANT: The jetty configuration is updated to include script-src: 'wasm-unsafe-eval' to allow WASM code execution which may be considered a security issue. * Add dev-docs for UI development with Kotlin/Compose * Improve build times via development flag This commit adds a development flag to our gradle.properties that allows the selection of the build variant for the new AdminUI. When development enabled (default), Gradle will build a development instance and will have less secure configuration for the AdminUI to be able to attach debugging tools. When disabled, Gradle will optimize build output for the new Admin UI, but will also take longer to complete. Default is set to true to always build development locally and in CI/CD to avoid longer building times. Additionally, user is able to disable the new AdminUI via SOLR_ADMIN_UI_EXPERIMENTAL_DISABLED or by disabling the AdminUI. IMPORTANT: From this commit on, during releases, the development flag needs to be set explicitly to false, otherwise it will not generate an optimized Admin UI with improved CSP directives.
1 parent 520e649 commit bb27d3e

File tree

116 files changed

+8558
-41
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

116 files changed

+8558
-41
lines changed

.github/labeler.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ admin-ui:
9696
- changed-files:
9797
- any-glob-to-any-file:
9898
- solr/webapp/**
99+
- solr/ui/**
99100

100101
# Add 'prometheus-exporter' label
101102
prometheus-exporter:

.github/renovate.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
"enabledManagers": ["gradle", "github-actions"],
77
"includePaths": ["gradle/libs.versions.toml", "versions.*", "build.gradle", ".github/workflows/*"],
88
"postUpgradeTasks": {
9-
"commands": ["./gradlew resolveAndLockAll --write-locks", "./gradlew updateLicenses"],
9+
"commands": [
10+
"./gradlew resolveAndLockAll --write-locks",
11+
"./gradlew kotlinUpgradeYarnLock",
12+
"./gradlew updateLicenses"
13+
],
1014
"fileFilters": ["solr/licenses/*.sha1"],
1115
"executionMode": "branch"
1216
},

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ __pycache__
3636
gradle/wrapper/gradle-wrapper.jar
3737
.gradletasknamecache
3838

39+
# Kotlin
40+
.kotlin/
41+
3942
# WANT TO ADD MORE? You can tell Git without adding to this file:
4043
# See https://git-scm.com/docs/gitignore
4144
# In particular, if you have tools you use, add to $GIT_DIR/info/exclude or use core.excludesFile

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ plugins {
2525
alias(libs.plugins.owasp.dependencycheck)
2626
alias(libs.plugins.cutterslade.analyze)
2727
alias(libs.plugins.benmanes.versions)
28+
alias(libs.plugins.kotlin.multiplatform) apply false
2829
alias(libs.plugins.littlerobots.versioncatalogupdate) apply false
2930
alias(libs.plugins.thetaphi.forbiddenapis) apply false
3031
alias(libs.plugins.undercouch.download) apply false
@@ -38,6 +39,9 @@ plugins {
3839
rootProject.ext.minJavaVersionDefault = JavaVersion.toVersion(libs.versions.java.min.get())
3940
rootProject.ext.minJavaVersionSolrJ = JavaVersion.toVersion(libs.versions.java.solrj.get())
4041

42+
// Check development mode for entire project (defaults to true if 'production' not provided and set to true)
43+
rootProject.ext.development = !project.hasProperty('production') || project.findProperty('production') != 'true'
44+
4145
apply from: file('gradle/globals.gradle')
4246

4347
// General metadata.

dev-docs/dependency-upgrades.adoc

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,18 @@ Read the https://github.com/apache/solr/blob/main/help/dependencies.txt[help/dep
2626
explanation of how dependencies are managed.
2727

2828
== Manual dependency upgrades
29-
In order to upgrade a dependency, you need to run through a number of steps:
29+
To upgrade a dependency, you need to run through a number of steps:
3030

3131
1. Identify the available versions from e.g. https://search.maven.org[Maven Central]
3232
2. Update the version in `gradle/libs.versions.toml` file
33-
3. Run `./gradlew resolveAndLockAll` to re-generate lockfiles. Note that this may cause a cascading effect where
34-
the locked version of other dependencies also change.
35-
4. In case of a conflict, resolve the conflict according to `help/dependencies.txt`
36-
5. Update the license and notice files of the changed dependencies. See `help/dependencies.txt` for details.
37-
6. Run `./gradlew updateLicenses` to re-generate SHA1 checksums of the new jar files.
38-
7. Once in a while, a new version of a dependency will transitively bring in brand-new dependencies.
33+
3. Run `./gradlew resolveAndLockAll --write-locks` to re-generate lockfiles. Note that this may cause a cascading effect
34+
where the locked version of other dependencies also changes.
35+
4. Run `./gradlew kotlinUpgradeYarnLock` to update the kotlin-js-store lockfile used for the new UI.
36+
Most of the cases it will not have any changes.
37+
5. In case of a conflict, resolve the conflict according to `help/dependencies.txt`
38+
6. Update the license and notice files of the changed dependencies. See `help/dependencies.txt` for details.
39+
7. Run `./gradlew updateLicenses` to re-generate SHA1 checksums of the new jar files.
40+
8. Once in a while, a new version of a dependency will transitively bring in brand-new dependencies.
3941
You'll need to decide whether to keep or exclude them. See `help/dependencies.txt` for details.
4042

4143
=== Constraints and Version Alignment
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
= Component Development
2+
:toc: left
3+
4+
== Overview
5+
6+
The following list contains a possible approach for implementing a new UI component:
7+
8+
1. Create a new design or start with an existing design, see for example Figma
9+
2. Validate the use case and analyze the components that may be used for the implementation
10+
3. Create Composables that represent the UI component(s) and use placeholders for data population
11+
4. Create a component interface and implementation with the UI state and UI component interactions
12+
5. Create previews with preview component implementations to check the UI implementation
13+
6. Create a store and store provider for fetching resources and interacting with Solr backend
14+
7. Implement the client used by the store provider
15+
8. Write tests and test the new component
16+
9. If not already done, integrate the component in the existing application
17+
10. If not already done, extract resources like texts to allow internationalization and localization
18+
19+
It is recommended to take a look at existing components, so that you get a better understanding
20+
of how things are implemented, what they have in common, and how each technology is utilized.
21+
22+
== Component's Logic
23+
24+
=== Components (Decompose)
25+
26+
The component integration interacts with the UI composables and the state store.
27+
28+
The implementation of the component interface "catches" user inputs like clicks and passes them
29+
to the store as ``Intent``s. The intents are then handled by the store implementation and
30+
may send a request to the backend and / or update the store state. The component is consuming
31+
and mapping the store state to the UI state. So once the store state is updated, it will
32+
reflect the changes in the UI.
33+
34+
=== State Stores and Store Providers
35+
36+
The state stores manage the state of the application, but independent of the state that is
37+
represented in the UI. Instances are created by store providers that hold the logic of the
38+
store.
39+
40+
Store providers consist of three elements:
41+
42+
- an executor implementation that consumes actions and intents and creates messages and labels
43+
- a reducer that updates the store state with the messages produced by the executor
44+
- a function for retrieving an instance of the store
45+
46+
The store provider does also define the interface for the client that has to be provided in
47+
order for the executor to make API calls and interact with the Solr backend.
48+
49+
== Component's Visuals
50+
51+
=== Composables
52+
53+
Composables are the UI elements that are defined and styled. They can be seen as boxes, rows and
54+
columns that are nested and change their style and structure based on conditions, state and input.
55+
56+
There are many ways to get started, but the easiest way probably is to get familiar with the basics
57+
and try things out. The Figma designs make use of almost the same elements for designing,
58+
so the structure and configurations there may be mapped almost one-by-one in Compose code.
59+
60+
=== Styling
61+
62+
The styling in Compose is done via ``Modifier``s. Each composable should normally accept a modifier
63+
as a parameter, so that the user can customize specific visual parameters of the composable like
64+
width, height and alignment in the parent's composable.
65+
66+
Since we are using Material 3, you do not have to care much about colors, typography and shapes.
67+
These are configured for the entire app, and you only have to make use of the right properties
68+
that are provided by the theme.
69+
70+
=== Accessibility
71+
72+
Compose comes with many accessibility features that can be used to improve the user experience.
73+
74+
The simplest form of accessibility in a UI is probably the responsiveness of the UI. This is
75+
realized with `WindowSizeClass`. Some composables may use a wrapper (usually suffixed with
76+
`Content`) that checks the window size and loads different UI based on the dimensions of the
77+
current window.
78+
79+
Another accessibility feature is the resource loading based on the system's locale or the user's
80+
preference. This allows the UI to be displayed in the user's native language. For that, you have
81+
to simply provide translations in the Compose resources.
82+
83+
Another accessibility feature often underestimated is coloring. Some people with color vision
84+
deficiency may need a different theme, so that elements with problematic contrasts may be
85+
better visible again.
86+
87+
Additional accessibility features like font scaling, semantics for screen readers may also
88+
be considered. Jetpack Compose provides a https://developer.android.com/develop/ui/compose/accessibility[simplified overview]
89+
and https://developer.android.com/codelabs/jetpack-compose-accessibility#0[Codelabs] for getting started.
90+
91+
=== Navigation and Child Components
92+
93+
Some components may have navigation elements and will load other components inside a frame layout.
94+
Since components hold a hierarchical context that needs to be managed somehow, child components
95+
(also used in navigation) are instantiated in a slightly different manner.
96+
97+
Decompose provides https://arkivanov.github.io/Decompose/navigation/overview/[a few examples]
98+
and details of the process behind the navigation and child components.
99+
100+
== Additional Notes
101+
102+
=== Dependency Locking
103+
104+
When adding or changing dependencies, you typically run `./gradlew resolveAndLockAll --write-locks`.
105+
Since we are building a web application from kotlin sources, we also have to update the JS lockfile
106+
with `./gradlew kotlinUpgradeYarnLock`. This will update the lockfile found at `kotlin-js-store/yarn.lock`.
107+
108+
Some multiplatform libraries have platform-specific dependency resolution that will result in different
109+
lockfiles being generated, based on the environment the lock task is executed. It is important to exclude
110+
these platform-specific libraries from the lockfile to ensure a consistent lockfile generation across
111+
different operating systems.
112+
113+
Platform-specific libraries come with a module name suffix that includes the platform name, like
114+
in `org.jetbrains.compose.desktop:desktop-jvm-windows-x64`. To identify those, look into the
115+
changes after updating the lockfile and add the necessary ignore-clause if such libraries
116+
exist. These ignore-clauses should be added in `gradle/validation/dependencies.gradle` inside the
117+
`allprojects.dependencyLocking` block.

dev-docs/ui/introduction.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
= New UI
2+
3+
== Introduction
4+
5+
The new UI that is introduced is a standalone frontend client that makes use of Solr's API.
6+
It is written in Kotlin and uses Compose Multiplatform as the UI framework.
7+
8+
== Overview
9+
10+
Since UI development mostly relies on different technologies and frameworks than backends use,
11+
the documentation is covering the following topics for new and experienced developers:
12+
13+
- Technology Overview
14+
- Module Structure and Elements
15+
- Component Development
16+
- Testing and Deployment
17+
18+
== Notes
19+
20+
All the references to files and directories in the UI documentation are from within
21+
the `solr/ui` module, if not otherwise stated.

dev-docs/ui/module-structure.adoc

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
= Module Structure and Elements
2+
3+
== Module Structure
4+
5+
The `ui` module follows a quite simple structure. It is split in
6+
7+
- *components*, which covers the logic part of the frontend and therefore contains
8+
the interfaces and implementations of them,
9+
- the *UI* part, which covers the visual elements, styling and everything related to UI, and
10+
- the *utils*, which contains various utilities used in both UI and logic.
11+
12+
In general, both UI and logic of the frontend are separated in "components" that follow similar,
13+
if not the same, structure and files.
14+
15+
== Components (Logic)
16+
17+
The logical part of a simple component (`org/apache/solr/ui/components`) usually consists of:
18+
19+
- **The component interface** (`[ComponentName]Component.kt`), that defines the UI state and the
20+
interaction options,
21+
22+
- **The store interface** (`store/[ComponentName]Store.kt`), that defines the (Solr) API state,
23+
intents for interacting with the state and optional labels (fire-and-forget events),
24+
25+
- **The store provider** (`store/[ComponentName]StoreProvider.kt`), that defines an API client
26+
interface that is used for making requests against the Solr API by consuming intents,
27+
updates the state and publishes labels,
28+
29+
- **The implementations** (`integration/*`), including component-specific mappings
30+
(`integration/Mappers.kt`) and interface implementations (like
31+
`integration/[Variant][ComponentName]Component.kt`),
32+
33+
- **Component-specific data classes** (`domain/*`), that are used only by the UI module
34+
35+
- **Component-specific API classes** (`data/*`), that are used for representing API requests /
36+
responses.
37+
38+
Some components may use multiple stores to consume different API endpoints or no store at all
39+
if no API interaction is necessary. Multiple components may also be used to simplify the complexity
40+
or separate the responsibilities into smaller UI elements.
41+
42+
Component data classes for API and internal use may also be merged and used interchangeably to
43+
reduce overall complexity. However, this affects the separation of concerns and may require
44+
in a later state the separation and mapping again.
45+
46+
This structure is strongly inspired by Decompose's https://arkivanov.github.io/Decompose/samples/[samples].
47+
48+
== User Interface (UI)
49+
50+
Similar to the logical part, the UI classes are also separated in components under
51+
`org.apache.solr.ui.views`.
52+
53+
Components may consist of one or multiple composables that make up a screen, section or
54+
element. The composables may also be reused, which is why they may be moved at some point
55+
during development to `.ui.components`.
56+
57+
Some vector assets like logos may be migrated to `ImageVector` and placed in `.ui.icons`
58+
to later be used in the UI.
59+
60+
Theme-related classes, functions and variables are all placed inside `.ui.theme`. The UI
61+
is making strong use of and customizes Material 3. You can find more information about
62+
Compose and Material 3 at https://m3.material.io/develop/android/jetpack-compose[Material Design - Jetpack Compose].
63+
64+
Composables may accept a component interface and be stateful, or simply hoist the entire
65+
state via parameters and be stateless. For more information about state hoisting,
66+
you can have a look at https://developer.android.com/develop/ui/compose/state[Managing state].
67+
68+
=== Compose Resources
69+
70+
Compose Resources are similar to assets in a web app and can be found at
71+
`commonMain/composeResources`. They contain files like fonts, translations, images,
72+
raw files and more.
73+
74+
The resource directories can use "qualifiers" that allow files to have variants.
75+
The most common use case is having translations for any text loaded. The directory
76+
`values/strings.xml` is the default / fallback strings resource file, that holds all values
77+
in english. If we would like to add translations for german now, we could use
78+
`values-de/strings.xml` and translate individual strings to german. Any string that is used
79+
and has no translation will automatically fall back to the value stored in `values/strings.xml`.
80+
81+
At the moment of writing, the language, theme and density qualifier are supported.
82+
83+
For more information about Compose Resources, see
84+
https://www.jetbrains.com/help/kotlin-multiplatform-dev/compose-images-resources.html[Multiplatform Resources].
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
= Technology Overview
2+
:toc: left
3+
4+
This document lists information about the technologies used in the UI module.
5+
6+
== Kotlin
7+
8+
Kotlin is a modern, statically typed programming language that runs on the Java Virtual
9+
Machine (JVM) and can also be compiled to JavaScript or native code. It is designed to be
10+
fully interoperable with Java, making it an excellent choice for UI and
11+
server-side development.
12+
13+
If you are new to Kotlin, it is strongly recommended to browse and go through the
14+
https://kotlinlang.org/[language documentation]. Since the UI module makes use of Kotlin's
15+
Multiplatform capabilities, you should also have a look at https://kotlinlang.org/docs/multiplatform.html[Kotlin Multiplatform].
16+
17+
=== Coroutines
18+
19+
Kotlin Coroutines provide a powerful and flexible way to manage asynchronous programming.
20+
They allow developers to write code that is sequential in nature but non-blocking under the hood,
21+
enabling efficient and straightforward concurrent programming. Coroutines simplify tasks such as
22+
making network requests, processing large datasets, or handling multiple user interactions
23+
simultaneously. They are integrated into the Kotlin language through libraries like
24+
kotlinx.coroutines.
25+
26+
To get started with Kotlin Coroutines, you can have a look at https://kotlinlang.org/docs/coroutines-overview.html[Coroutines Overview].
27+
28+
=== Ktor
29+
30+
https://ktor.io/[Ktor] is a Kotlin framework designed for building asynchronous servers and clients.
31+
It provides a robust toolkit for making HTTP requests and handling responses, making it an
32+
excellent choice for frontend applications that need to communicate with a backend server.
33+
Ktor's client library is highly customizable and supports various features essential for
34+
frontend development.
35+
36+
== Compose Multiplatform
37+
38+
Compose Multiplatform is a UI framework that enables developers to create user interfaces
39+
for Android, iOS, desktop, and web applications using a single codebase. It is built on
40+
the principles of Jetpack Compose, providing a declarative way to build UIs.
41+
42+
To get started with Compose Multiplatform, see the https://www.jetbrains.com/lp/compose-multiplatform/[Compose Multiplatform page].
43+
Many resources from https://developer.android.com/compose[Jetpack Compose] may be used for learning
44+
and as reference, but keep in mind that some of the information may not have a multiplatform
45+
integration yet.
46+
47+
=== Material 3
48+
49+
Material 3, also known as Material You, is the latest version of Google's Material Design system.
50+
It offers new components, dynamic theming, and updated guidelines to create a more personalized
51+
and adaptive user experience.
52+
53+
The https://m3.material.io/[Material 3 website] gives a good introduction into user interface
54+
design and Material 3.
55+
56+
=== Decompose
57+
58+
Decompose is a library for managing the lifecycle of components in Compose Multiplatform
59+
applications. It provides tools for navigating between screens and managing state across
60+
different parts of an application.
61+
62+
The main features of Decompose include navigation, state management and component lifecycle.
63+
You can find out more and get started via the https://arkivanov.github.io/Decompose/[Decompose documentation].
64+
65+
=== MVIKotlin
66+
67+
MVIKotlin is a library for implementing the Model-View-Intent (MVI) architecture pattern in
68+
Kotlin applications. It is particularly useful for managing complex state and side effects in a
69+
predictable manner. In combination with Decompose it provides a great foundation and
70+
unified structure for the UI components.
71+
72+
You can find out more at https://arkivanov.github.io/MVIKotlin/.
73+
74+
=== Essenty
75+
76+
Essenty is a collection of utility libraries that complement the Kotlin ecosystem,
77+
particularly useful in Compose Multiplatform projects. It includes tools for handling events,
78+
lifecycles, and other common tasks in a type-safe and idiomatic way.
79+
80+
Similar to MVIKotlin, it adds the foundation and structure of the UI module and plays an
81+
essential part in Decompose.
82+
83+
For more information, you can have a look at https://github.com/arkivanov/Essenty[GitHub - Essenty].

0 commit comments

Comments
 (0)