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,
)
}
}