Skip to content

SOLR-17658: Implement start screen #3388

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

Merged
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,7 @@ kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serializa
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-contentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-mock = { module = "io.ktor:ktor-client-mock", version.ref = "ktor" }
ktor-client-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
langchain4j-cohere = { module = "dev.langchain4j:langchain4j-cohere", version.ref = "langchain4j" }
langchain4j-core = { module = "dev.langchain4j:langchain4j-core", version.ref = "langchain4j" }
Expand Down
1 change: 1 addition & 0 deletions solr/ui/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ kotlin {
implementation(libs.kotlinx.coroutines.test)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
implementation(compose.uiTest)
implementation(libs.ktor.client.mock)
}
}

Expand Down
3 changes: 3 additions & 0 deletions solr/ui/gradle.lockfile
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ io.ktor:ktor-client-content-negotiation:3.1.0=allSourceSetsCompileDependenciesMe
io.ktor:ktor-client-core-jvm:3.1.0=desktopCompileClasspath,desktopRuntimeClasspath,desktopTestCompileClasspath,desktopTestRuntimeClasspath
io.ktor:ktor-client-core-wasm-js:3.1.0=wasmJsCompileClasspath,wasmJsNpmAggregated,wasmJsRuntimeClasspath,wasmJsTestCompileClasspath,wasmJsTestNpmAggregated,wasmJsTestRuntimeClasspath
io.ktor:ktor-client-core:3.1.0=allSourceSetsCompileDependenciesMetadata,allTestSourceSetsCompileDependenciesMetadata,commonMainApiDependenciesMetadata,commonMainCompileOnlyDependenciesMetadata,commonMainImplementationDependenciesMetadata,commonMainResolvableDependenciesMetadata,commonTestApiDependenciesMetadata,commonTestCompileOnlyDependenciesMetadata,commonTestImplementationDependenciesMetadata,commonTestResolvableDependenciesMetadata,desktopCompileClasspath,desktopMainApiDependenciesMetadata,desktopMainCompileOnlyDependenciesMetadata,desktopMainImplementationDependenciesMetadata,desktopMainResolvableDependenciesMetadata,desktopRuntimeClasspath,desktopTestApiDependenciesMetadata,desktopTestCompileClasspath,desktopTestCompileOnlyDependenciesMetadata,desktopTestImplementationDependenciesMetadata,desktopTestResolvableDependenciesMetadata,desktopTestRuntimeClasspath,metadataCommonMainCompileClasspath,metadataCompileClasspath,wasmJsCompileClasspath,wasmJsMainApiDependenciesMetadata,wasmJsMainCompileOnlyDependenciesMetadata,wasmJsMainImplementationDependenciesMetadata,wasmJsMainResolvableDependenciesMetadata,wasmJsNpmAggregated,wasmJsRuntimeClasspath,wasmJsTestApiDependenciesMetadata,wasmJsTestCompileClasspath,wasmJsTestCompileOnlyDependenciesMetadata,wasmJsTestImplementationDependenciesMetadata,wasmJsTestNpmAggregated,wasmJsTestResolvableDependenciesMetadata,wasmJsTestRuntimeClasspath
io.ktor:ktor-client-mock-jvm:3.1.0=desktopTestCompileClasspath,desktopTestRuntimeClasspath
io.ktor:ktor-client-mock-wasm-js:3.1.0=wasmJsTestCompileClasspath,wasmJsTestNpmAggregated,wasmJsTestRuntimeClasspath
io.ktor:ktor-client-mock:3.1.0=allTestSourceSetsCompileDependenciesMetadata,commonTestApiDependenciesMetadata,commonTestCompileOnlyDependenciesMetadata,commonTestImplementationDependenciesMetadata,commonTestResolvableDependenciesMetadata,desktopTestApiDependenciesMetadata,desktopTestCompileClasspath,desktopTestCompileOnlyDependenciesMetadata,desktopTestImplementationDependenciesMetadata,desktopTestResolvableDependenciesMetadata,desktopTestRuntimeClasspath,wasmJsTestApiDependenciesMetadata,wasmJsTestCompileClasspath,wasmJsTestCompileOnlyDependenciesMetadata,wasmJsTestImplementationDependenciesMetadata,wasmJsTestNpmAggregated,wasmJsTestResolvableDependenciesMetadata,wasmJsTestRuntimeClasspath
io.ktor:ktor-events-jvm:3.1.0=desktopCompileClasspath,desktopRuntimeClasspath,desktopTestCompileClasspath,desktopTestRuntimeClasspath
io.ktor:ktor-events-wasm-js:3.1.0=wasmJsCompileClasspath,wasmJsNpmAggregated,wasmJsRuntimeClasspath,wasmJsTestCompileClasspath,wasmJsTestNpmAggregated,wasmJsTestRuntimeClasspath
io.ktor:ktor-events:3.1.0=allSourceSetsCompileDependenciesMetadata,allTestSourceSetsCompileDependenciesMetadata,commonMainApiDependenciesMetadata,commonMainCompileOnlyDependenciesMetadata,commonMainImplementationDependenciesMetadata,commonMainResolvableDependenciesMetadata,commonTestApiDependenciesMetadata,commonTestCompileOnlyDependenciesMetadata,commonTestImplementationDependenciesMetadata,commonTestResolvableDependenciesMetadata,desktopCompileClasspath,desktopMainApiDependenciesMetadata,desktopMainCompileOnlyDependenciesMetadata,desktopMainImplementationDependenciesMetadata,desktopMainResolvableDependenciesMetadata,desktopRuntimeClasspath,desktopTestApiDependenciesMetadata,desktopTestCompileClasspath,desktopTestCompileOnlyDependenciesMetadata,desktopTestImplementationDependenciesMetadata,desktopTestResolvableDependenciesMetadata,desktopTestRuntimeClasspath,metadataCommonMainCompileClasspath,metadataCompileClasspath,wasmJsCompileClasspath,wasmJsMainApiDependenciesMetadata,wasmJsMainCompileOnlyDependenciesMetadata,wasmJsMainImplementationDependenciesMetadata,wasmJsMainResolvableDependenciesMetadata,wasmJsNpmAggregated,wasmJsRuntimeClasspath,wasmJsTestApiDependenciesMetadata,wasmJsTestCompileClasspath,wasmJsTestCompileOnlyDependenciesMetadata,wasmJsTestImplementationDependenciesMetadata,wasmJsTestNpmAggregated,wasmJsTestResolvableDependenciesMetadata,wasmJsTestRuntimeClasspath
Expand Down
10 changes: 10 additions & 0 deletions solr/ui/src/commonMain/composeResources/drawable/solr-sun.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 21 additions & 3 deletions solr/ui/src/commonMain/composeResources/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@
-->

<resources>
<!-- Content Descriptions (CD) -->
<!-- Actions (action) -->
<string name="action_connect">Connect</string>
<string name="action_logout">Logout</string>

<!-- Content Descriptions (cd) -->
<string name="cd_solr_logo">Solr Logo</string>

<!-- Navigation (NAV) -->
<!-- Descriptions (desc) -->
<string name="desc_to_get_started">To get started, please provide a Solr host URL:</string>

<!-- Errors (error) -->
<string name="error_invalid_url">The provided URL is invalid.</string>
<string name="error_solr_host_not_found">Solr host could not be found.</string>
<string name="error_unknown">An unknown error occurred.</string>

<!-- Navigation (nav) -->
<string name="nav_cluster">Cluster</string>
<string name="nav_collections">Collections</string>
<string name="nav_configsets">Configsets</string>
Expand All @@ -32,15 +44,21 @@
<string name="nav_security">Security</string>
<string name="nav_thread_dump">Thread Dump</string>

<!-- Placeholders (ph) -->
<string name="ph_solr_url">http://127.0.0.1:8983/</string>

<!-- Titles (title) -->
<string name="title_welcome_to_solr">Welcome to Solr Admin UI</string>

<!-- General Text -->
<string name="community">Community</string>
<string name="documentation">Documentation</string>
<string name="irc">IRC</string>
<string name="issue_tracker">Issue Tracker</string>
<string name="logout">Logout</string>
<string name="slack">Slack</string>
<string name="solr_query_syntax">Solr Query Syntax</string>
<string name="support">Support</string>
<string name="connecting">Connecting...</string>

<!-- Uncategorized -->
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.solr.ui.components.auth

interface UnauthenticatedComponent {

/**
* Aborts the authentication attempt.
*/
fun onAbort()

sealed interface Output {

/**
* Emitted when the user successfully authenticated against
* the Solr instance.
*/
data object OnConnected: Output

/**
* Emitted when the user aborts the authentication flow.
*/
data object OnAbort: Output
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.solr.ui.components.auth.integration

import com.arkivanov.mvikotlin.core.store.StoreFactory
import io.ktor.client.HttpClient
import org.apache.solr.ui.components.auth.UnauthenticatedComponent
import org.apache.solr.ui.utils.AppComponentContext

class DefaultUnauthenticatedComponent(
componentContext: AppComponentContext,
storeFactory: StoreFactory,
httpClient: HttpClient,
private val output: (UnauthenticatedComponent.Output) -> Unit
) : UnauthenticatedComponent, AppComponentContext by componentContext {

// TODO Implement me

override fun onAbort() = output(UnauthenticatedComponent.Output.OnAbort)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.arkivanov.mvikotlin.core.store.StoreFactory
import com.arkivanov.mvikotlin.extensions.coroutines.stateFlow
import io.ktor.client.HttpClient
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import org.apache.solr.ui.components.environment.EnvironmentComponent
import org.apache.solr.ui.components.environment.store.EnvironmentStoreProvider
import org.apache.solr.ui.utils.AppComponentContext
Expand All @@ -37,13 +38,15 @@ class DefaultEnvironmentComponent(
httpClient: HttpClient,
) : EnvironmentComponent, AppComponentContext by componentContext {

private val mainScope = coroutineScope(mainContext)
private val mainScope = coroutineScope(SupervisorJob() + mainContext)
private val ioScope = coroutineScope(SupervisorJob() + ioContext)

private val store = instanceKeeper.getStore {
EnvironmentStoreProvider(
storeFactory = storeFactory,
client = HttpEnvironmentStoreClient(httpClient),
ioContext = ioContext,
mainContext = mainScope.coroutineContext,
ioContext = ioScope.coroutineContext,
).provide()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import org.apache.solr.ui.components.environment.store.EnvironmentStore.State
internal class EnvironmentStoreProvider(
private val storeFactory: StoreFactory,
private val client: Client,
private val mainContext: CoroutineContext,
private val ioContext: CoroutineContext,
) {

Expand All @@ -58,7 +59,7 @@ internal class EnvironmentStoreProvider(
/**
* Action used for initiating the initial fetch of environment data.
*/
data object FetchInitialSystemData: Action
data object FetchInitialSystemData : Action
}

private sealed interface Message {
Expand All @@ -78,9 +79,10 @@ internal class EnvironmentStoreProvider(
data class JavaPropertiesUpdated(val properties: List<JavaProperty>) : Message
}

private inner class ExecutorImpl : CoroutineExecutor<Intent, Action, State, Message, Nothing>() {
private inner class ExecutorImpl :
CoroutineExecutor<Intent, Action, State, Message, Nothing>(mainContext) {

override fun executeAction(action: Action) = when(action) {
override fun executeAction(action: Action) = when (action) {
Action.FetchInitialSystemData -> {
fetchSystemData()
fetchJavaProperties()
Expand Down Expand Up @@ -145,6 +147,7 @@ internal class EnvironmentStoreProvider(
system = msg.data.system,
node = msg.data.node,
)

is Message.JavaPropertiesUpdated -> copy(
javaProperties = msg.properties,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ package org.apache.solr.ui.components.root

import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.value.Value
import org.apache.solr.ui.components.auth.UnauthenticatedComponent
import org.apache.solr.ui.components.main.MainComponent
import org.apache.solr.ui.components.start.StartComponent

/**
* Root component used by each target as an entry point to the application.
Expand All @@ -33,9 +35,10 @@ interface RootComponent {

sealed interface Child {

data class Start(val component: StartComponent): Child

data class Main(val component: MainComponent): Child

// TODO Add child once authentication is checked
// data class Unauthenticated(val component: UnauthenticatedComponent): Child
data class Unauthenticated(val component: UnauthenticatedComponent): Child
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ package org.apache.solr.ui.components.root.integration
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.decompose.router.stack.replaceAll
import com.arkivanov.decompose.value.Value
import com.arkivanov.mvikotlin.core.store.StoreFactory
import io.ktor.client.HttpClient
import kotlinx.serialization.Serializable
import org.apache.solr.ui.components.auth.UnauthenticatedComponent
import org.apache.solr.ui.components.auth.integration.DefaultUnauthenticatedComponent
import org.apache.solr.ui.components.main.MainComponent
import org.apache.solr.ui.components.main.integration.DefaultMainComponent
import org.apache.solr.ui.components.root.RootComponent
import org.apache.solr.ui.components.root.RootComponent.Child.*
import org.apache.solr.ui.components.start.StartComponent
import org.apache.solr.ui.components.start.integration.DefaultStartComponent
import org.apache.solr.ui.utils.AppComponentContext

/**
Expand All @@ -39,14 +47,16 @@ import org.apache.solr.ui.utils.AppComponentContext
class SimpleRootComponent(
componentContext: AppComponentContext,
storeFactory: StoreFactory,
private val startComponent: (AppComponentContext, (StartComponent.Output) -> Unit) -> StartComponent,
private val mainComponent: (AppComponentContext) -> MainComponent,
private val unauthenticatedComponent: (AppComponentContext, (UnauthenticatedComponent.Output) -> Unit) -> UnauthenticatedComponent,
) : RootComponent, AppComponentContext by componentContext {

private val navigation = StackNavigation<Configuration>()
private val stack = childStack(
source = navigation,
serializer = Configuration.serializer(),
initialStack = { listOf(Configuration.Main) },
initialStack = { listOf(Configuration.Start) },
handleBackButton = true,
childFactory = ::createChild
)
Expand All @@ -61,6 +71,14 @@ class SimpleRootComponent(
) : this(
componentContext = componentContext,
storeFactory = storeFactory,
startComponent = { childContext, output ->
DefaultStartComponent(
componentContext = childContext,
storeFactory = storeFactory,
httpClient = httpClient,
output = output,
)
},
mainComponent = { childContext ->
DefaultMainComponent(
componentContext = childContext,
Expand All @@ -69,18 +87,60 @@ class SimpleRootComponent(
destination = destination,
)
},
unauthenticatedComponent = { childContext, output ->
DefaultUnauthenticatedComponent(
componentContext = childContext,
storeFactory = storeFactory,
httpClient = httpClient,
output = output,
)
}
)

private fun createChild(
configuration: Configuration,
componentContext: AppComponentContext,
): RootComponent.Child = when (configuration) {
Configuration.Main -> RootComponent.Child.Main(mainComponent(componentContext))
Configuration.Start -> Start(startComponent(componentContext, ::startOutput))
Configuration.Main -> Main(mainComponent(componentContext))
Configuration.Unauthenticated -> Unauthenticated(
unauthenticatedComponent(
componentContext,
::unauthenticatedOutput
)
)
}

/**
* Output handler for any output returned by the [StartComponent].
*
* @param output The output returned by the start component implementation.
*/
private fun startOutput(output: StartComponent.Output) = when (output) {
StartComponent.Output.OnAuthRequired -> navigation.pushNew(Configuration.Unauthenticated)
StartComponent.Output.OnConnected ->
navigation.replaceAll(Configuration.Main)
}

/**
* Output handler for any output returned by the [UnauthenticatedComponent].
*
* @param output The output returned by the unauthenticated component implementation.
*/
private fun unauthenticatedOutput(output: UnauthenticatedComponent.Output) = when (output) {
UnauthenticatedComponent.Output.OnConnected -> navigation.replaceAll(Configuration.Main)
UnauthenticatedComponent.Output.OnAbort -> navigation.pop()
}

@Serializable
private sealed interface Configuration {

@Serializable
data object Start : Configuration

@Serializable
data object Unauthenticated : Configuration

@Serializable
data object Main : Configuration
}
Expand Down
Loading
Loading