From a448f6ddc77bc9c4112a64b5684bc61b0e7a5cee Mon Sep 17 00:00:00 2001 From: Jonathan Almeida Date: Fri, 22 May 2026 23:01:10 -0400 Subject: [PATCH] Add timestamp to Try pushes. --- app/detekt-baseline.xml | 4 +- .../tryfox/ui/screens/ProfileScreenTest.kt | 8 ++ .../org/mozilla/tryfox/TryFoxViewModel.kt | 7 ++ .../tryfox/ui/composables/PushCommentCard.kt | 79 ++++++++++++++++++- .../mozilla/tryfox/ui/models/PushUiModel.kt | 1 + .../tryfox/ui/screens/ProfileScreen.kt | 1 + .../tryfox/ui/screens/ProfileViewModel.kt | 1 + .../tryfox/ui/screens/TreeherderApksScreen.kt | 8 +- 8 files changed, 103 insertions(+), 6 deletions(-) diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml index f504878..996a0e3 100644 --- a/app/detekt-baseline.xml +++ b/app/detekt-baseline.xml @@ -35,7 +35,7 @@ FunctionNaming:ProfileScreen.kt$@Composable private fun ProfileSearchButton( onClick: () -> Unit, enabled: Boolean, isLoading: Boolean, modifier: Modifier = Modifier ) FunctionNaming:ProfileScreen.kt$@OptIn(ExperimentalComposeUiApi::class) @Composable private fun UserSearchCard( email: String, onEmailChange: (String) -> Unit, onSearchClick: () -> Unit, isLoading: Boolean, modifier: Modifier = Modifier ) FunctionNaming:ProfileScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ProfileScreen( modifier: Modifier = Modifier, onNavigateUp: () -> Unit, profileViewModel: ProfileViewModel ) - FunctionNaming:PushCommentCard.kt$@Composable fun PushCommentCard(comment: String, author: String?, revision: String) + FunctionNaming:PushCommentCard.kt$@Composable fun PushCommentCard( comment: String, author: String?, revision: String, pushTimestamp: Long, ) FunctionNaming:Theme.kt$@Composable fun FenixInstallerTheme( darkTheme: Boolean = isSystemInDarkTheme(), // Dynamic color is available on Android 12+ dynamicColor: Boolean = true, content: @Composable () -> Unit ) FunctionNaming:TreeherderApksScreen.kt$@Composable fun ErrorState(errorMessage: String) FunctionNaming:TreeherderApksScreen.kt$@Composable fun LoadingState() @@ -52,7 +52,7 @@ LongMethod:ProfileScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ProfileScreen( modifier: Modifier = Modifier, onNavigateUp: () -> Unit, profileViewModel: ProfileViewModel ) LongMethod:ProfileViewModel.kt$ProfileViewModel$fun downloadArtifact(artifactUiModel: ArtifactUiModel, context: Context) LongMethod:ProfileViewModel.kt$ProfileViewModel$fun searchByAuthor(context: Context) - LongMethod:PushCommentCard.kt$@Composable fun PushCommentCard(comment: String, author: String?, revision: String) + LongMethod:PushCommentCard.kt$@Composable fun PushCommentCard( comment: String, author: String?, revision: String, pushTimestamp: Long, ) LongMethod:TreeherderApksScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun FenixInstallerApp( fenixInstallerViewModel: FenixInstallerViewModel, onNavigateUp: () -> Unit, ) LongMethod:TreeherderApksScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun SearchSection( selectedProject: String, onProjectSelected: (String) -> Unit, revision: String, onRevisionChange: (String) -> Unit, onSearchClick: () -> Unit, isLoading: Boolean ) LongParameterList:TreeherderApksScreen.kt$( selectedProject: String, onProjectSelected: (String) -> Unit, revision: String, onRevisionChange: (String) -> Unit, onSearchClick: () -> Unit, isLoading: Boolean ) diff --git a/app/src/androidTest/java/org/mozilla/tryfox/ui/screens/ProfileScreenTest.kt b/app/src/androidTest/java/org/mozilla/tryfox/ui/screens/ProfileScreenTest.kt index d6294ce..1c9ca10 100644 --- a/app/src/androidTest/java/org/mozilla/tryfox/ui/screens/ProfileScreenTest.kt +++ b/app/src/androidTest/java/org/mozilla/tryfox/ui/screens/ProfileScreenTest.kt @@ -73,6 +73,14 @@ class ProfileScreenTest { .fetchSemanticsNodes().isNotEmpty() } + val timestampChips = composeTestRule + .onAllNodesWithTag("push_timestamp_chip_fakerevision123", useUnmergedTree = true) + .fetchSemanticsNodes() + assertTrue( + "Expected a push timestamp chip to be rendered for Try push entry", + timestampChips.isNotEmpty(), + ) + composeTestRule.onNodeWithTag(downloadButtonInitialTag, useUnmergedTree = true) .performClick() diff --git a/app/src/main/java/org/mozilla/tryfox/TryFoxViewModel.kt b/app/src/main/java/org/mozilla/tryfox/TryFoxViewModel.kt index d6d5792..54bc01b 100644 --- a/app/src/main/java/org/mozilla/tryfox/TryFoxViewModel.kt +++ b/app/src/main/java/org/mozilla/tryfox/TryFoxViewModel.kt @@ -99,6 +99,9 @@ class TryFoxViewModel( var relevantPushAuthor by mutableStateOf(null) private set + var relevantPushTimestamp by mutableStateOf(null) + private set + var isLoading by mutableStateOf(false) private set @@ -161,6 +164,7 @@ class TryFoxViewModel( revision = newRevision relevantPushComment = null relevantPushAuthor = null + relevantPushTimestamp = null selectedJobs = emptyList() isLoadingJobArtifacts.clear() errorMessage = null @@ -197,6 +201,7 @@ class TryFoxViewModel( errorMessage = null relevantPushComment = null relevantPushAuthor = null + relevantPushTimestamp = null selectedJobs = emptyList() isLoadingJobArtifacts.clear() cacheManager.checkCacheStatus() // Use CacheManager @@ -216,8 +221,10 @@ class TryFoxViewModel( } } relevantPushAuthor = firstPushResult.author + relevantPushTimestamp = firstPushResult.pushTimestamp } else { relevantPushAuthor = null + relevantPushTimestamp = null } relevantPushComment = foundComment diff --git a/app/src/main/java/org/mozilla/tryfox/ui/composables/PushCommentCard.kt b/app/src/main/java/org/mozilla/tryfox/ui/composables/PushCommentCard.kt index 35e929c..01a0ef7 100644 --- a/app/src/main/java/org/mozilla/tryfox/ui/composables/PushCommentCard.kt +++ b/app/src/main/java/org/mozilla/tryfox/ui/composables/PushCommentCard.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Person +import androidx.compose.material.icons.filled.Schedule import androidx.compose.material3.AssistChip import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults @@ -28,7 +29,17 @@ import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withLink +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.unit.dp +import kotlinx.datetime.Instant +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.TimeZone +import kotlinx.datetime.format.FormatStringsInDatetimeFormats +import kotlinx.datetime.format.byUnicodePattern +import kotlinx.datetime.toLocalDateTime +import org.mozilla.tryfox.ui.theme.TryFoxTheme import java.util.regex.Pattern // Helper data class to store link information @@ -39,8 +50,14 @@ private data class LinkableSpan( val url: String, ) +@OptIn(FormatStringsInDatetimeFormats::class) @Composable -fun PushCommentCard(comment: String, author: String?, revision: String) { // Added revision parameter +fun PushCommentCard( + comment: String, + author: String?, + revision: String, + pushTimestamp: Long, +) { val urlPattern = remember { Pattern.compile( "(https?://|www\\.)" + // Scheme or www. @@ -150,6 +167,66 @@ fun PushCommentCard(comment: String, author: String?, revision: String) { // Add }, ) } + + val formattedTimestamp = remember(pushTimestamp) { + val format = LocalDateTime.Format { byUnicodePattern("yyyy-MM-dd HH:mm") } + format.format( + Instant.fromEpochSeconds(pushTimestamp) + .toLocalDateTime(TimeZone.currentSystemDefault()), + ) + } + AssistChip( + onClick = { }, + label = { Text(formattedTimestamp) }, + leadingIcon = { + Icon( + imageVector = Icons.Filled.Schedule, + contentDescription = "Push timestamp", + ) + }, + modifier = Modifier.testTag("push_timestamp_chip_$revision"), + ) } } } + +private data class PushCommentCardPreviewState( + val name: String, + val comment: String, + val author: String?, +) + +private class PushCommentCardStateProvider : PreviewParameterProvider { + override val values: Sequence = sequenceOf( + PushCommentCardPreviewState( + name = "With bug and URL", + comment = "Bug 1234567 - Fix flaky test. See https://treeherder.mozilla.org/jobs for details.", + author = "developer@mozilla.com", + ), + PushCommentCardPreviewState( + name = "Plain text, no author", + comment = "Try push for performance investigation on macOS.", + author = null, + ), + PushCommentCardPreviewState( + name = "Multiple bugs", + comment = "Bug 1000001, Bug 1000002 - Backout for build bustage.", + author = "releng@mozilla.com", + ), + ) +} + +@Preview(showBackground = true, widthDp = 360) +@Composable +private fun PushCommentCardPreview( + @PreviewParameter(PushCommentCardStateProvider::class) state: PushCommentCardPreviewState, +) { + TryFoxTheme { + PushCommentCard( + comment = state.comment, + author = state.author, + revision = "abc123def456", + pushTimestamp = 1_716_460_800L, + ) + } +} diff --git a/app/src/main/java/org/mozilla/tryfox/ui/models/PushUiModel.kt b/app/src/main/java/org/mozilla/tryfox/ui/models/PushUiModel.kt index b738f82..a2ec752 100644 --- a/app/src/main/java/org/mozilla/tryfox/ui/models/PushUiModel.kt +++ b/app/src/main/java/org/mozilla/tryfox/ui/models/PushUiModel.kt @@ -5,4 +5,5 @@ data class PushUiModel( val author: String, val jobs: List, val revision: String?, + val pushTimestamp: Long, ) diff --git a/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileScreen.kt b/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileScreen.kt index d870480..5c6bd6e 100644 --- a/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileScreen.kt +++ b/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileScreen.kt @@ -304,6 +304,7 @@ fun ProfileScreen( comment = push.pushComment, author = push.author, revision = push.revision ?: "unknown_revision", + pushTimestamp = push.pushTimestamp, ) push.jobs.forEach { job -> JobCard(job = job, profileViewModel = profileViewModel) diff --git a/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileViewModel.kt b/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileViewModel.kt index a6195d2..c0fed7d 100644 --- a/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileViewModel.kt +++ b/app/src/main/java/org/mozilla/tryfox/ui/screens/ProfileViewModel.kt @@ -174,6 +174,7 @@ class ProfileViewModel( author = pushResult.author, jobs = jobsWithArtifacts, revision = pushResult.revision, + pushTimestamp = pushResult.pushTimestamp, ) } else { logcat(LogPriority.VERBOSE, TAG) { diff --git a/app/src/main/java/org/mozilla/tryfox/ui/screens/TreeherderApksScreen.kt b/app/src/main/java/org/mozilla/tryfox/ui/screens/TreeherderApksScreen.kt index a562991..9d43bab 100644 --- a/app/src/main/java/org/mozilla/tryfox/ui/screens/TreeherderApksScreen.kt +++ b/app/src/main/java/org/mozilla/tryfox/ui/screens/TreeherderApksScreen.kt @@ -167,12 +167,14 @@ fun TryFoxMainScreen( } tryFoxViewModel.relevantPushComment?.let { comment -> - if (comment.isNotBlank() || tryFoxViewModel.relevantPushAuthor != null) { // Show card if comment or author exists + val pushTimestamp = tryFoxViewModel.relevantPushTimestamp + if ((comment.isNotBlank() || tryFoxViewModel.relevantPushAuthor != null) && pushTimestamp != null) { item { PushCommentCard( - comment = comment ?: "", + comment = comment, author = tryFoxViewModel.relevantPushAuthor, - revision = tryFoxViewModel.revision, // Added revision + revision = tryFoxViewModel.revision, + pushTimestamp = pushTimestamp, ) } }