Skip to content

Commit

Permalink
Bids 3162/summary chart v2 5 (gobitfly#577)
Browse files Browse the repository at this point in the history
* summary chart 2.5 adaptations
  • Loading branch information
MauserBitfly authored Jul 11, 2024
1 parent 6cc2d5c commit 26b358b
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 44 deletions.
5 changes: 3 additions & 2 deletions frontend/components/bc/BcDropdown.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
<script setup lang="ts">
interface Props {
variant?: 'default' | 'table' | 'header'
variant?: 'default' | 'table' | 'header',
panelClass?: string
}
defineProps<Props>()
</script>

<template>
<Dropdown :class="variant" :panel-class="variant">
<Dropdown :class="variant" :panel-class="[variant, panelClass]">
<template #value="slotProps">
<slot name="value" v-bind="slotProps" />
</template>
Expand Down
7 changes: 6 additions & 1 deletion frontend/components/bc/premium/BcPremiumGem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'

<template>
<BcTooltip :text="$t('premium.subscribe')">
<BcLink :to="`/pricing`" target="_blank">
<BcLink :to="`/pricing`" target="_blank" class="link">
<div>
<FontAwesomeIcon :icon="faGem" class="gem" />
</div>
Expand All @@ -20,4 +20,9 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
color: var(--primary-color);
cursor: pointer;
}
.link {
cursor: pointer;
pointer-events: all;
}
</style>
6 changes: 3 additions & 3 deletions frontend/components/bc/table/BcTableControl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ const onInput = (value: string) => {
<div class="bc-table-header">
<div class="side left">
<BcIconToggle v-if="$slots.chart" v-model="tableIsShown" :true-icon="faTable" :false-icon="faChartColumn" :disabled="chartDisabled" />
<BcIconToggle v-if="useAbsoluteValues !== null" v-model="useAbsoluteValues" :true-icon="faHashtag" :false-icon="faPercent" />
<BcIconToggle v-if="useAbsoluteValues !== null && tableIsShown" v-model="useAbsoluteValues" :true-icon="faHashtag" :false-icon="faPercent" />
<slot name="header-left" />
</div>

<slot name="header-center">
<slot name="header-center" :table-is-shown="tableIsShown">
<div v-if="props.title" class="h1">
{{ props.title }}
</div>
</slot>
<div class="side right">
<slot name="header-right" />
<slot name="header-right" :table-is-shown="tableIsShown" />
<BcContentFilter
v-if="props.searchPlaceholder && tableIsShown"
:search-placeholder="props.searchPlaceholder"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<script lang="ts" setup>
import { orderBy } from 'lodash-es'
import { type AggregationTimeframe, AggregationTimeframes, type EfficiencyType, EfficiencyTypes, SUMMARY_CHART_GROUP_NETWORK_AVERAGE, SUMMARY_CHART_GROUP_TOTAL, type SummaryChartFilter } from '~/types/dashboard/summary'
import { getGroupLabel } from '~/utils/dashboard/group'
const { t: $t } = useI18n()
const { overview } = useValidatorDashboardOverviewStore()
const chartFilter = defineModel<SummaryChartFilter>({ required: true })
/** aggregation */
const aggregation = ref<AggregationTimeframe>(chartFilter.value.aggregation)
const aggregationList = AggregationTimeframes.map(a => ({
id: a,
label: $t(`time_frames.${a}`)
}))
watch(aggregation, (a) => { chartFilter.value.aggregation = a })
const aggregationDisabled = ({ id }: { id: AggregationTimeframe }) => (overview.value?.chart_history_seconds[id] ?? 0) === 0
/** efficiency */
const efficiency = ref<EfficiencyType>(chartFilter.value.efficiency)
const efficiencyList = EfficiencyTypes.map(e => ({
id: e,
label: $t(`dashboard.validator.summary.chart.efficiency.${e}`)
}))
watch(efficiency, (e) => { chartFilter.value.efficiency = e })
/** groups */
const total = ref(!chartFilter.value.initialised || chartFilter.value.groupIds.includes(SUMMARY_CHART_GROUP_TOTAL))
const average = ref(!chartFilter.value.initialised || chartFilter.value.groupIds.includes(SUMMARY_CHART_GROUP_NETWORK_AVERAGE))
const groups = computed(() => {
if (!overview.value?.groups) {
return []
}
return orderBy(overview.value.groups.filter(g => !!g.count), [g => g.name.toLowerCase()], 'asc')
})
const selectedGroups = ref<number[]>([])
watch(groups, (list) => {
// when groups change we reset the selected groups
selectedGroups.value = list.map(g => g.id)
}, { immediate: true })
watch([selectedGroups, total, average], ([list, t, a]) => {
const groupIds: number[] = [...list]
if (t) {
groupIds.push(SUMMARY_CHART_GROUP_TOTAL)
}
if (a) {
groupIds.push(SUMMARY_CHART_GROUP_NETWORK_AVERAGE)
}
chartFilter.value.groupIds = groupIds
chartFilter.value.initialised = true
}, { immediate: true })
const selectAllGroups = () => {
selectedGroups.value = groups.value.map(g => g.id)
}
const toggleGroups = () => {
if (selectedGroups.value.length < groups.value.length) {
selectAllGroups()
} else {
selectedGroups.value = []
}
}
const selectedLabel = computed(() => {
const list: string[] = orderBy(selectedGroups.value.map(id => getGroupLabel($t, id, groups.value)), [g => g.toLowerCase()], 'asc')
if (average.value) {
list.splice(0, 0, $t('dashboard.validator.summary.chart.average'))
}
if (total.value) {
list.splice(0, 0, $t('dashboard.validator.summary.chart.total'))
}
if (!list.length) {
return $t('dashboard.group.selection.all')
}
return list.join(', ')
})
</script>

<template>
<div class="chart-filter-row">
<BcDropdown
v-model="aggregation"
:options="aggregationList"
option-value="id"
option-label="label"
:option-disabled="aggregationDisabled"
panel-class="summary-chart-aggregation-panel"
class="small"
>
<template #option="slotProps">
<span>{{ slotProps.label }}</span>
<BcPremiumGem class="premium-gem" @click.stop="() => undefined" />
</template>
</BcDropdown>
<BcDropdown v-model="efficiency" :options="efficiencyList" option-value="id" option-label="label" class="small" />

<MultiSelect
v-model="selectedGroups"
:options="groups"
option-label="name"
option-value="id"
:placeholder="$t('dashboard.group.selection.all')"
>
<template #header>
<div class="special-groups">
<Checkbox v-model="total" input-id="total" :binary="true" />
<label for="total">{{ $t("dashboard.validator.summary.chart.total") }}</label>
</div>
<div class="special-groups">
<Checkbox v-model="average" input-id="average" :binary="true" />
<label for="average">{{ $t("dashboard.validator.summary.chart.average") }}</label>
</div>
<span class="pointer" @click="toggleGroups">
{{ $t('dashboard.group.selection.all') }}
</span>
</template>
<template #value>
{{ selectedLabel }}
</template>
</MultiSelect>
</div>
</template>
<style lang="scss" scoped>
.chart-filter-row {
display: flex;
gap: var(--padding);
:deep(>.p-multiselect),
:deep(>.p-dropdown){
max-width: 200px;
@media (max-width: 1000px) {
max-width: 76px;
}
}
}
.special-groups {
display: flex;
gap: var(--padding);
padding-left: var(--padding-small);
margin-bottom: var(--padding);
}
:global(.summary-chart-aggregation-panel .p-dropdown-item) {
display: flex;
gap: var(--padding-small);
align-items: center;
}
:global(.summary-chart-aggregation-panel .p-dropdown-item:not(.p-disabled) .premium-gem) {
display: none;
}
</style>
58 changes: 41 additions & 17 deletions frontend/components/dashboard/chart/SummaryChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import {
} from 'echarts/components'
import VChart from 'vue-echarts'
import SummaryChartTooltip from './SummaryChartTooltip.vue'
import { useFormat } from '~/composables/useFormat'
import { getSummaryChartGroupColors, getChartTextColor, getChartTooltipBackgroundColor } from '~/utils/colors'
import { type InternalGetValidatorDashboardSummaryChartResponse } from '~/types/api/validator_dashboard'
import { type ChartData } from '~/types/api/common'
import { getGroupLabel } from '~/utils/dashboard/group'
import { API_PATH } from '~/types/customFetch'
import { SUMMARY_CHART_GROUP_NETWORK_AVERAGE, SUMMARY_CHART_GROUP_TOTAL, type AggregationTimeframe, type SummaryChartFilter } from '~/types/dashboard/summary'
use([
CanvasRenderer,
Expand All @@ -28,23 +28,44 @@ use([
GridComponent
])
interface Props {
filter?: SummaryChartFilter
}
const props = defineProps<Props>()
const { fetch } = useCustomFetch()
const { formatEpochToDate } = useFormat()
const { tsToEpoch } = useNetworkStore()
const { dashboardKey } = useDashboardKey()
const data = ref<ChartData<number, number> | undefined >()
const { value: filter, bounce: bounceFilter } = useDebounceValue(props.filter, 1000)
const aggregation = ref<AggregationTimeframe>('hourly')
const isLoading = ref(false)
await useAsyncData('validator_dashboard_summary_chart', async () => {
const loadData = async () => {
if (!dashboardKey.value) {
data.value = undefined
return
}
isLoading.value = true
const res = await fetch<InternalGetValidatorDashboardSummaryChartResponse>(API_PATH.DASHBOARD_SUMMARY_CHART, undefined, { dashboardKey: dashboardKey.value })
const requestAggregation = props.filter?.aggregation || 'hourly'
const res = await fetch<InternalGetValidatorDashboardSummaryChartResponse>(API_PATH.DASHBOARD_SUMMARY_CHART, { query: { group_ids: props.filter?.groupIds.join(','), efficiency_type: props.filter?.efficiency, aggregation: requestAggregation } }, { dashboardKey: dashboardKey.value })
aggregation.value = requestAggregation
isLoading.value = false
data.value = res.data
}, { watch: [dashboardKey], server: false })
}
watch([dashboardKey, filter], () => {
loadData()
}, { immediate: true })
watch(() => props.filter, (filter) => {
if (!filter) {
return
}
bounceFilter({ ...filter, groupIds: [...filter.groupIds] }, true, true)
}, { immediate: true, deep: true })
const { groups } = useValidatorDashboardGroups()
Expand Down Expand Up @@ -82,7 +103,14 @@ const option = computed(() => {
if (data.value?.series) {
const allGroups = $t('dashboard.validator.summary.chart.all_groups')
data.value.series.forEach((element) => {
const name = getGroupLabel($t, element.id, groups.value, allGroups)
let name: string
if (element.id === SUMMARY_CHART_GROUP_TOTAL) {
name = $t('dashboard.validator.summary.chart.total')
} else if (element.id === SUMMARY_CHART_GROUP_NETWORK_AVERAGE) {
name = $t('dashboard.validator.summary.chart.average')
} else {
name = getGroupLabel($t, element.id, groups.value, allGroups)
}
const newObj: SeriesObject = {
data: element.data,
type: 'line',
Expand All @@ -109,17 +137,16 @@ const option = computed(() => {
fontSize: textSize,
lineHeight: 20,
formatter: (value: number) => {
const date = formatEpochToDate(value, $t('locales.date'))
if (date === undefined) {
return ''
const date = formatGoTimestamp(value, undefined, 'absolute', 'narrow', $t('locales.date'), false)
if (aggregation.value === 'epoch') {
return `${date}\n${$t('common.epoch')} ${tsToEpoch(value)}`
}
return `${date}\n${$t('common.epoch')} ${value}`
return date
}
}
},
yAxis: {
name: $t('dashboard.validator.summary.chart.efficiency'),
name: $t(`dashboard.validator.summary.chart.efficiency.${props.filter?.efficiency}`),
nameLocation: 'center',
nameTextStyle: {
padding: [0, 0, 30, 0]
Expand Down Expand Up @@ -162,11 +189,8 @@ const option = computed(() => {
trigger: 'axis',
padding: 0,
borderColor: colors.value.background,
valueFormatter: (value: number) => {
return `${value}% ${$t('dashboard.validator.summary.chart.efficiency')}`
},
formatter (params : any) : HTMLElement {
const startEpoch = parseInt(params[0].axisValue)
const ts = parseInt(params[0].axisValue)
const groupInfos = params.map((param: any) => {
return {
name: param.seriesName,
Expand All @@ -176,7 +200,7 @@ const option = computed(() => {
})
const d = document.createElement('div')
render(h(SummaryChartTooltip, { t: $t, startEpoch, groupInfos }), d)
render(h(SummaryChartTooltip, { t: $t, ts, efficiencyType: props.filter?.efficiency || 'all', aggregation: aggregation.value, groupInfos }), d)
return d
}
},
Expand Down
11 changes: 6 additions & 5 deletions frontend/components/dashboard/chart/SummaryChartTooltip.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<script lang="ts" setup>
import { type ComposerTranslation } from 'vue-i18n'
import { type AggregationTimeframe, type EfficiencyType } from '~/types/dashboard/summary'
interface Props {
t: ComposerTranslation, // required as dynamically created components via render do not have the proper app context
startEpoch: number,
ts: number,
aggregation: AggregationTimeframe,
efficiencyType: EfficiencyType,
groupInfos: {
name: string,
efficiency: number,
Expand All @@ -17,15 +20,13 @@ defineProps<Props>()

<template>
<div class="tooltip-container">
<DashboardChartTooltipHeader :t="t" :start-epoch="startEpoch" />
<DashboardChartTooltipHeader :t="t" :ts="ts" :aggregation="aggregation" :efficiency-type="efficiencyType" />
<div v-for="(entry, index) in groupInfos" :key="index" class="line-container">
<div class="circle" :style="{ 'background-color': entry.color }" />
<div>
{{ entry.name }}:
</div>
<div class="efficiency">
{{ entry.efficiency }}%
</div>
<BcFormatPercent class="efficiency" :percent="entry.efficiency" />
</div>
</div>
</template>
Expand Down
Loading

0 comments on commit 26b358b

Please sign in to comment.