diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 550236c1b..2bcc4313f 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -47,13 +47,6 @@ jobs: # happen in practice. run: docker run -d -p 3306:3306 --name mysql -e MYSQL_ALLOW_EMPTY_PASSWORD=1 mysql:5.7 --sql-mode="" - - name: Cache Gradle packages - uses: actions/cache@v1 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - restore-keys: ${{ runner.os }}-gradle - - name: Test run: gradle build check -i --parallel --build-cache diff --git a/.github/workflows/Publish-Website.yml b/.github/workflows/Publish-Website.yml index 3aefd39e9..158bb5977 100644 --- a/.github/workflows/Publish-Website.yml +++ b/.github/workflows/Publish-Website.yml @@ -25,13 +25,6 @@ jobs: - name: Hermit run: ./bin/hermit env -r >> $GITHUB_ENV - - name: Cache Gradle packages - uses: actions/cache@v1 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - restore-keys: ${{ runner.os }}-gradle - - name: Prep mkdocs run: .github/workflows/prepare_mkdocs.sh diff --git a/build.gradle.kts b/build.gradle.kts index a51f10125..dcbc1596d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,7 @@ import com.vanniktech.maven.publish.SonatypeHost import org.gradle.api.tasks.testing.logging.TestExceptionFormat import org.jetbrains.dokka.gradle.DokkaTask import org.jetbrains.kotlin.allopen.gradle.AllOpenExtension +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile buildscript { @@ -39,8 +40,8 @@ subprojects { tasks.withType { dependsOn("spotlessKotlinApply") - kotlinOptions { - jvmTarget = "11" + compilerOptions { + jvmTarget = JvmTarget.JVM_11 } } diff --git a/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/BackfilaSqlDelightGradlePlugin.kt b/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/BackfilaSqlDelightGradlePlugin.kt index d78d5f29a..25d21aa5b 100644 --- a/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/BackfilaSqlDelightGradlePlugin.kt +++ b/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/BackfilaSqlDelightGradlePlugin.kt @@ -7,7 +7,6 @@ import java.io.Serializable import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.file.Directory import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.tasks.TaskProvider diff --git a/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/GenerateBackfilaRecordSourceSqlTask.kt b/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/GenerateBackfilaRecordSourceSqlTask.kt index d4a1cde62..5a9fa2d20 100644 --- a/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/GenerateBackfilaRecordSourceSqlTask.kt +++ b/client-sqldelight-gradle-plugin/src/main/kotlin/app/cash/backfila/client/sqldelight/plugin/GenerateBackfilaRecordSourceSqlTask.kt @@ -1,12 +1,12 @@ package app.cash.backfila.client.sqldelight.plugin +import java.io.File import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Input import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction -import java.io.File abstract class GenerateBackfilaRecordSourceSqlTask : DefaultTask() { @get:Input diff --git a/client-sqldelight-gradle-plugin/src/test/projects/happyPath/lib/build.gradle.kts b/client-sqldelight-gradle-plugin/src/test/projects/happyPath/lib/build.gradle.kts index ddb3cac2d..0e5110649 100644 --- a/client-sqldelight-gradle-plugin/src/test/projects/happyPath/lib/build.gradle.kts +++ b/client-sqldelight-gradle-plugin/src/test/projects/happyPath/lib/build.gradle.kts @@ -13,7 +13,7 @@ sqldelight { dialect(libs.sqldelightMysqlDialect) srcDirs.setFrom(listOf("resources/migrations")) deriveSchemaFromMigrations.set(true) - migrationOutputDirectory.set(file("$buildDir/resources/main/migrations")) + migrationOutputDirectory.set(layout.buildDirectory.dir("resources/main/migrations")) verifyMigrations.set(true) } } @@ -36,4 +36,4 @@ dependencies { implementation("app.cash.backfila:client-sqldelight:${project.property("backfilaVersion")}") implementation("app.cash.backfila:client:${project.property("backfilaVersion")}") implementation(libs.sqldelightJdbcDriver) -} \ No newline at end of file +} diff --git a/client-sqldelight-test/build.gradle.kts b/client-sqldelight-test/build.gradle.kts index 8a88344ff..6ee5f6f39 100644 --- a/client-sqldelight-test/build.gradle.kts +++ b/client-sqldelight-test/build.gradle.kts @@ -12,7 +12,7 @@ sqldelight { dialect(libs.sqldelightMysqlDialect) srcDirs.setFrom(listOf("src/main/sqldelight", "src/main/resources/migrations")) deriveSchemaFromMigrations.set(true) - migrationOutputDirectory.set(file("$buildDir/resources/main/migrations")) + migrationOutputDirectory.set(layout.buildDirectory.dir("resources/main/migrations")) verifyMigrations.set(true) } } diff --git a/client/build.gradle.kts b/client/build.gradle.kts index a945b41bc..76ac86ca1 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -11,7 +11,7 @@ plugins { sourceSets { val main by getting { - java.srcDir("$buildDir/generated/source/wire/") + java.srcDir(layout.buildDirectory.dir("generated/source/wire")) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 35501f758..1f0275df4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,11 +1,11 @@ [versions] -jooq = "3.18.1" -ktlint = "0.47.1" +jooq = "3.18.25" kotlin = "1.9.23" +ktlint = "0.47.1" misk = "2025.01.09.184726-680bca2" okhttp = "5.0.0-alpha.14" -wire = "5.0.0" sqldelight = "2.0.2" +wire = "5.2.1" [libraries] apacheCommonsLang3 = { module = "org.apache.commons:commons-lang3", version = "3.12.0" } @@ -40,15 +40,15 @@ moshiKotlin = { module = "com.squareup.moshi:moshi-kotlin", version = "1.13.0" } mysql = { module = "mysql:mysql-connector-java", version = "8.0.30" } okHttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okHttpMockWebServer = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okhttp" } -okio = { module = "com.squareup.okio:okio", version = "3.9.0" } +okio = { module = "com.squareup.okio:okio", version = "3.9.1" } openTracing = { module = "io.opentracing:opentracing-api", version = "0.33.0" } openTracingMock = { module = "io.opentracing:opentracing-mock", version = "0.33.0" } openTracingOkHttp = { module = "io.opentracing.contrib:opentracing-okhttp3", version = "3.0.0" } -retrofit = { module = "com.squareup.retrofit2:retrofit", version = "2.9.0" } -retrofitGuavaAdapter = { module = "com.squareup.retrofit2:adapter-guava", version = "2.9.0" } -retrofitMock = { module = "com.squareup.retrofit2:retrofit-mock", version = "2.9.0" } -retrofitMoshi = { module = "com.squareup.retrofit2:converter-moshi", version = "2.9.0" } -retrofitWire = { module = "com.squareup.retrofit2:converter-wire", version = "2.9.0" } +retrofit = { module = "com.squareup.retrofit2:retrofit", version = "2.11.0" } +retrofitGuavaAdapter = { module = "com.squareup.retrofit2:adapter-guava", version = "2.11.0" } +retrofitMock = { module = "com.squareup.retrofit2:retrofit-mock", version = "2.11.0" } +retrofitMoshi = { module = "com.squareup.retrofit2:converter-moshi", version = "2.11.0" } +retrofitWire = { module = "com.squareup.retrofit2:converter-wire", version = "2.11.0" } shadowJarPlugin = { module = "gradle.plugin.com.github.johnrengelman:shadow", version = "7.1.2" } slf4jApi = { module = "org.slf4j:slf4j-api", version = "2.0.3" } sqldelightGradlePlugin = { module = "app.cash.sqldelight:gradle-plugin", version.ref = "sqldelight" } diff --git a/service/src/main/kotlin/app/cash/backfila/dashboard/ViewLogsAction.kt b/service/src/main/kotlin/app/cash/backfila/dashboard/ViewLogsAction.kt index 6db8552f8..c59520e59 100644 --- a/service/src/main/kotlin/app/cash/backfila/dashboard/ViewLogsAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/dashboard/ViewLogsAction.kt @@ -31,15 +31,17 @@ class ViewLogsAction @Inject constructor( fun viewLogs( @PathParam id: Long, ): Response { - val url = transacter.transaction { session -> - val backfillRun = session.loadOrNull(Id(id)) - ?: throw BadRequestException("backfill $id doesn't exist") - viewLogsUrlProvider.getUrl(session, backfillRun) - } + val url = getUrl(id) return Response( body = "go to $url".toResponseBody(), statusCode = HttpURLConnection.HTTP_MOVED_TEMP, headers = Headers.headersOf("Location", url), ) } + + fun getUrl(id: Long) = transacter.transaction { session -> + val backfillRun = session.loadOrNull(Id(id)) + ?: throw BadRequestException("backfill $id doesn't exist") + viewLogsUrlProvider.getUrl(session, backfillRun) + } } diff --git a/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt index 390c0fbad..3126a42a9 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/actions/BackfillShowButtonHandlerAction.kt @@ -84,5 +84,6 @@ class BackfillShowButtonHandlerAction @Inject constructor( companion object { const val PATH = "/api/backfill/{id}/update" fun path(id: String) = PATH.replace("{id}", id) + fun path(id: Long) = path(id.toString()) } } diff --git a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt index 5be88def4..6b3d5b84e 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/pages/BackfillShowAction.kt @@ -2,6 +2,7 @@ package app.cash.backfila.ui.pages import app.cash.backfila.dashboard.GetBackfillStatusAction import app.cash.backfila.dashboard.GetBackfillStatusResponse +import app.cash.backfila.dashboard.ViewLogsAction import app.cash.backfila.service.persistence.BackfillState import app.cash.backfila.ui.actions.BackfillShowButtonHandlerAction import app.cash.backfila.ui.components.AutoReload @@ -45,14 +46,15 @@ import misk.web.mediatype.MediaTypes class BackfillShowAction @Inject constructor( private val getBackfillStatusAction: GetBackfillStatusAction, private val dashboardPageLayout: DashboardPageLayout, + private val viewLogsAction: ViewLogsAction, ) : WebAction { @Get(PATH) @ResponseContentType(MediaTypes.TEXT_HTML) @Authenticated(capabilities = ["users"]) fun get( - @PathParam id: String, + @PathParam id: Long, ): Response { - val backfill = getBackfillStatusAction.status(id.toLong()) + val backfill = getBackfillStatusAction.status(id) val label = if (backfill.variant == "default") backfill.service_name else "${backfill.service_name} (${backfill.variant})" @@ -79,12 +81,12 @@ class BackfillShowAction @Inject constructor( ) .buildHtmlResponseBody { AutoReload { - PageTitle("Backfill", id) { + PageTitle("Backfill", id.toString()) { a { href = BackfillCreateAction.path( service = backfill.service_name, - variantOrBackfillNameOrId = if (backfill.variant != "default") backfill.variant else id, - backfillNameOrId = if (backfill.variant != "default") id else "", + variantOrBackfillNameOrId = if (backfill.variant != "default") backfill.variant else id.toString(), + backfillNameOrId = if (backfill.variant != "default") id.toString() else "", ) button(classes = "rounded-full bg-indigo-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600") { @@ -241,26 +243,26 @@ class BackfillShowAction @Inject constructor( val updateFieldId: String? = null, ) - private fun getStateButton(state: BackfillState, id: String): Link? { + private fun getStateButton(state: BackfillState): Link? { return when (state) { BackfillState.PAUSED -> Link( - label = "Start", + label = START_STATE_BUTTON_LABEL, href = BackfillState.RUNNING.name, ) BackfillState.COMPLETE -> null else -> Link( - label = "Pause", + label = PAUSE_STATE_BUTTON_LABEL, href = BackfillState.PAUSED.name, ) } } - private fun GetBackfillStatusResponse.toConfigurationRows(id: String) = listOf( + private fun GetBackfillStatusResponse.toConfigurationRows(id: Long) = listOf( DescriptionListRow( label = "State", description = state.name, - button = getStateButton(state, id), + button = getStateButton(state), updateFieldId = "state", ), DescriptionListRow( @@ -319,10 +321,9 @@ class BackfillShowAction @Inject constructor( DescriptionListRow( label = "Logs", description = "", - // TODO add link real URL button = Link( - label = "View", - href = "#", + label = VIEW_LOGS_BUTTON_LABEL, + href = viewLogsAction.getUrl(id), ), ), ) + if (parameters?.isNotEmpty() == true) { @@ -348,7 +349,7 @@ class BackfillShowAction @Inject constructor( } } - private fun TagConsumer<*>.ConfigurationRows(id: String, it: DescriptionListRow) { + private fun TagConsumer<*>.ConfigurationRows(id: Long, it: DescriptionListRow) { div("px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0") { attributes["data-controller"] = "toggle" @@ -420,6 +421,21 @@ class BackfillShowAction @Inject constructor( +"Cancel" } } + } else if (button.label == VIEW_LOGS_BUTTON_LABEL) { + span("ml-4 flex-shrink-0") { + // View logs button will link to external logs provider + a { + href = button.href + target = "_blank" + + button( + classes = "rounded-md font-medium text-indigo-600 hover:text-indigo-500", + ) { + type = ButtonType.submit + +button.label + } + } + } } else { span("ml-4 flex-shrink-0") { // Button when clicked updates without additional form @@ -440,8 +456,14 @@ class BackfillShowAction @Inject constructor( } } + val buttonStyle = if (it.updateFieldId == "state") { + val color = if (it.button.label == START_STATE_BUTTON_LABEL) "green" else "yellow" + "rounded-full bg-$color-600 px-3 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-$color-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-$color-600" + } else { + "rounded-md font-medium text-indigo-600 hover:text-indigo-500" + } button( - classes = "rounded-md font-medium text-indigo-600 hover:text-indigo-500", + classes = buttonStyle, ) { type = ButtonType.submit +button.label @@ -457,7 +479,11 @@ class BackfillShowAction @Inject constructor( companion object { private const val PATH = "/backfills/{id}" fun path(id: String) = PATH.replace("{id}", id) + fun path(id: Long) = path(id.toString()) + const val START_STATE_BUTTON_LABEL = "Start" + const val PAUSE_STATE_BUTTON_LABEL = "Pause" const val UPDATE_BUTTON_LABEL = "Update" + const val VIEW_LOGS_BUTTON_LABEL = "View Logs" } } diff --git a/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt b/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt index effd63d2c..18dc652cb 100644 --- a/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt +++ b/service/src/main/kotlin/app/cash/backfila/ui/pages/ServiceShowAction.kt @@ -14,7 +14,6 @@ import kotlinx.html.button import misk.scope.ActionScoped import misk.security.authz.Authenticated import misk.tailwind.Link -import misk.tokens.TokenGenerator import misk.web.Get import misk.web.HttpCall import misk.web.PathParam @@ -31,7 +30,6 @@ class ServiceShowAction @Inject constructor( private val clientHttpCall: ActionScoped, private val dashboardPageLayout: DashboardPageLayout, private val getBackfillRunsAction: GetBackfillRunsAction, - private val tokenGenerator: TokenGenerator, ) : WebAction { private val path by lazy { clientHttpCall.get().url.encodedPath diff --git a/service/src/test/kotlin/app/cash/backfila/development/BackfilaDevelopmentService.kt b/service/src/test/kotlin/app/cash/backfila/development/BackfilaDevelopmentService.kt index 9c79979c3..524283f83 100644 --- a/service/src/test/kotlin/app/cash/backfila/development/BackfilaDevelopmentService.kt +++ b/service/src/test/kotlin/app/cash/backfila/development/BackfilaDevelopmentService.kt @@ -129,7 +129,7 @@ fun main(args: Array) { internal class DevelopmentViewLogsUrlProvider : ViewLogsUrlProvider { override fun getUrl(session: Session, backfillRun: DbBackfillRun): String { - return "/" + return "/${backfillRun.service.registry_name}/${backfillRun.service.variant}/${backfillRun.id}/logs" } }