feat: add countries, devices, browsers, OS dashboard sections
Add 4 new dimension queries (concurrent with existing 4, total 8) to the dashboard: countries, devices, browsers, and operating systems. All reuse the existing DimensionSection component with proportional progress bars. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d9e4b18a52
commit
54a5b38fc6
8 changed files with 184 additions and 2 deletions
|
|
@ -134,16 +134,80 @@ class StatsRepository @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
val countriesDeferred = async {
|
||||
api.query(
|
||||
baseUrl, apiKey,
|
||||
QueryRequest(
|
||||
siteId = siteId,
|
||||
metrics = listOf("visitors"),
|
||||
dateRange = apiDateRange,
|
||||
dimensions = listOf("visit:country_name"),
|
||||
orderBy = listOf(listOf("visitors", "desc")),
|
||||
pagination = Pagination(limit = 10)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val devicesDeferred = async {
|
||||
api.query(
|
||||
baseUrl, apiKey,
|
||||
QueryRequest(
|
||||
siteId = siteId,
|
||||
metrics = listOf("visitors"),
|
||||
dateRange = apiDateRange,
|
||||
dimensions = listOf("visit:device"),
|
||||
orderBy = listOf(listOf("visitors", "desc")),
|
||||
pagination = Pagination(limit = 10)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val browsersDeferred = async {
|
||||
api.query(
|
||||
baseUrl, apiKey,
|
||||
QueryRequest(
|
||||
siteId = siteId,
|
||||
metrics = listOf("visitors"),
|
||||
dateRange = apiDateRange,
|
||||
dimensions = listOf("visit:browser"),
|
||||
orderBy = listOf(listOf("visitors", "desc")),
|
||||
pagination = Pagination(limit = 10)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val osDeferred = async {
|
||||
api.query(
|
||||
baseUrl, apiKey,
|
||||
QueryRequest(
|
||||
siteId = siteId,
|
||||
metrics = listOf("visitors"),
|
||||
dateRange = apiDateRange,
|
||||
dimensions = listOf("visit:os"),
|
||||
orderBy = listOf(listOf("visitors", "desc")),
|
||||
pagination = Pagination(limit = 10)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val topStats = parseTopStats(topStatsDeferred.await())
|
||||
val timeSeries = parseTimeSeries(timeSeriesDeferred.await())
|
||||
val topSources = parseDimension(topSourcesDeferred.await())
|
||||
val topPages = parseDimension(topPagesDeferred.await())
|
||||
val countries = parseDimension(countriesDeferred.await())
|
||||
val devices = parseDimension(devicesDeferred.await())
|
||||
val browsers = parseDimension(browsersDeferred.await())
|
||||
val operatingSystems = parseDimension(osDeferred.await())
|
||||
|
||||
DashboardData(
|
||||
topStats = topStats,
|
||||
timeSeries = timeSeries,
|
||||
topSources = topSources,
|
||||
topPages = topPages
|
||||
topPages = topPages,
|
||||
countries = countries,
|
||||
devices = devices,
|
||||
browsers = browsers,
|
||||
operatingSystems = operatingSystems
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,5 +12,9 @@ data class DashboardData(
|
|||
val topStats: TopStatsData,
|
||||
val timeSeries: List<TimeSeriesPoint>,
|
||||
val topSources: List<DimensionEntry>,
|
||||
val topPages: List<DimensionEntry>
|
||||
val topPages: List<DimensionEntry>,
|
||||
val countries: List<DimensionEntry> = emptyList(),
|
||||
val devices: List<DimensionEntry> = emptyList(),
|
||||
val browsers: List<DimensionEntry> = emptyList(),
|
||||
val operatingSystems: List<DimensionEntry> = emptyList()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@ import no.naiv.implausibly.ui.common.UiState
|
|||
import no.naiv.implausibly.ui.dashboard.components.DateRangeSelector
|
||||
import no.naiv.implausibly.ui.dashboard.components.StatCard
|
||||
import no.naiv.implausibly.ui.dashboard.components.VisitorChart
|
||||
import no.naiv.implausibly.ui.dashboard.sections.BrowsersSection
|
||||
import no.naiv.implausibly.ui.dashboard.sections.CountriesSection
|
||||
import no.naiv.implausibly.ui.dashboard.sections.DevicesSection
|
||||
import no.naiv.implausibly.ui.dashboard.sections.OperatingSystemsSection
|
||||
import no.naiv.implausibly.ui.dashboard.sections.TopPagesSection
|
||||
import no.naiv.implausibly.ui.dashboard.sections.TopSourcesSection
|
||||
|
||||
|
|
@ -130,6 +134,22 @@ private fun DashboardContent(
|
|||
TopPagesSection(entries = data.topPages)
|
||||
}
|
||||
|
||||
item {
|
||||
CountriesSection(entries = data.countries)
|
||||
}
|
||||
|
||||
item {
|
||||
DevicesSection(entries = data.devices)
|
||||
}
|
||||
|
||||
item {
|
||||
BrowsersSection(entries = data.browsers)
|
||||
}
|
||||
|
||||
item {
|
||||
OperatingSystemsSection(entries = data.operatingSystems)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package no.naiv.implausibly.ui.dashboard.sections
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import no.naiv.implausibly.domain.model.DimensionEntry
|
||||
|
||||
@Composable
|
||||
fun BrowsersSection(
|
||||
entries: List<DimensionEntry>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DimensionSection(
|
||||
title = "Browsers",
|
||||
entries = entries,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package no.naiv.implausibly.ui.dashboard.sections
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import no.naiv.implausibly.domain.model.DimensionEntry
|
||||
|
||||
@Composable
|
||||
fun CountriesSection(
|
||||
entries: List<DimensionEntry>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DimensionSection(
|
||||
title = "Countries",
|
||||
entries = entries,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package no.naiv.implausibly.ui.dashboard.sections
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import no.naiv.implausibly.domain.model.DimensionEntry
|
||||
|
||||
@Composable
|
||||
fun DevicesSection(
|
||||
entries: List<DimensionEntry>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DimensionSection(
|
||||
title = "Devices",
|
||||
entries = entries,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
package no.naiv.implausibly.ui.dashboard.sections
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import no.naiv.implausibly.domain.model.DimensionEntry
|
||||
|
||||
@Composable
|
||||
fun OperatingSystemsSection(
|
||||
entries: List<DimensionEntry>,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
DimensionSection(
|
||||
title = "Operating Systems",
|
||||
entries = entries,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
|
@ -102,6 +102,24 @@ class StatsRepositoryTest {
|
|||
)
|
||||
)
|
||||
|
||||
// Mock countries, devices, browsers, OS responses
|
||||
coEvery { api.query(any(), any(), match { "visit:country_name" in it.dimensions }) } returns
|
||||
QueryResponse(results = listOf(
|
||||
QueryResult(dimensions = listOf(JsonPrimitive("Germany")), metrics = listOf(JsonPrimitive(50)))
|
||||
))
|
||||
coEvery { api.query(any(), any(), match { "visit:device" in it.dimensions }) } returns
|
||||
QueryResponse(results = listOf(
|
||||
QueryResult(dimensions = listOf(JsonPrimitive("Desktop")), metrics = listOf(JsonPrimitive(80)))
|
||||
))
|
||||
coEvery { api.query(any(), any(), match { "visit:browser" in it.dimensions }) } returns
|
||||
QueryResponse(results = listOf(
|
||||
QueryResult(dimensions = listOf(JsonPrimitive("Firefox")), metrics = listOf(JsonPrimitive(60)))
|
||||
))
|
||||
coEvery { api.query(any(), any(), match { "visit:os" in it.dimensions }) } returns
|
||||
QueryResponse(results = listOf(
|
||||
QueryResult(dimensions = listOf(JsonPrimitive("Linux")), metrics = listOf(JsonPrimitive(40)))
|
||||
))
|
||||
|
||||
val result = repository.getDashboardData(
|
||||
instance = testInstance,
|
||||
apiKey = "test-key",
|
||||
|
|
@ -118,6 +136,10 @@ class StatsRepositoryTest {
|
|||
assertEquals("Google", result.topSources[0].name)
|
||||
assertEquals(1, result.topPages.size)
|
||||
assertEquals("/blog", result.topPages[0].name)
|
||||
assertEquals("Germany", result.countries[0].name)
|
||||
assertEquals("Desktop", result.devices[0].name)
|
||||
assertEquals("Firefox", result.browsers[0].name)
|
||||
assertEquals("Linux", result.operatingSystems[0].name)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue