Skip to content

Commit

Permalink
[issue-19] Heatmap of active week day and hours #19
Browse files Browse the repository at this point in the history
 Added new stats for dasboard with heatmap
 Refactorings like changed class names
 Addded tests

 Closes #19
  • Loading branch information
michmzr committed Jun 29, 2023
1 parent 059652b commit 3c2bf39
Show file tree
Hide file tree
Showing 18 changed files with 1,520 additions and 885 deletions.
1,947 changes: 1,082 additions & 865 deletions src/frontend/package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@fortawesome/free-regular-svg-icons": "^6.3.0",
"@fortawesome/free-solid-svg-icons": "^6.3.0",
"@fortawesome/vue-fontawesome": "^3.0.3",
"apexcharts": "3.22.3",
"axios": "^1.3.3",
"bootstrap": "^5.2.3",
"bootstrap-vue-3": "^0.5.1",
Expand All @@ -23,8 +24,10 @@
"vue": "^3.2.47",
"vue-axios": "^3.5.2",
"vue-chartjs": "^5.0.0",
"vue-class-component": "^8.0.0-0",
"vue-class-component": "^8.0.0-rc.1",
"vue-property-decorator": "^9.1.2",
"vue-router": "^4.0.3",
"vue3-apexcharts": "^1.4.1",
"vue3-datepicker": "^0.3.4",
"vuex": "^4.0.0"
},
Expand Down
1 change: 0 additions & 1 deletion src/frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
</div>
</main>
</template>

<script>
import {defineComponent} from "vue";
import TopAuth from "@/components/TopAuth.vue";
Expand Down
146 changes: 146 additions & 0 deletions src/frontend/src/components/HeatmapArchivedChart.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<template>
<div v-if="authorized && loadedData" class="card">
<div class="card-body">
<h5 class="card-title">{{ header }}</h5>

<div style="width: 100%">
<apexchart :options="options" :series="series" type="heatmap"></apexchart>
</div>
</div>
</div>
</template>

<script lang="ts">
import {Vue} from "vue-class-component";
import {Prop} from 'vue-property-decorator';
import {useSessionStore, useSyncStore} from "@/store";
import {StatsService} from "@/services/stats-service";
import {HeatmapType, IActivityHeatmapItem, IActivityHeatmapStats,} from "@/models/stats-models";
export default class HeatmapArchivedChart extends Vue {
@Prop({type: String, required: true})
header!: string;
@Prop({type: String, default: HeatmapType.ARCHIVED})
type!: string;
authorized: Boolean = false
loadedData: boolean = true
sessionStore = useSessionStore()
syncStore = useSyncStore()
statsService = new StatsService()
items: IActivityHeatmapItem[] = []
options = {
chart: {
id: 'heatmap',
width: '100%'
},
plotOptions: {
heatmap: {
distributed: true,
colorScale: {
ranges: [
{from: 0, to: 30, color: '#00A100', name: 'very low'},
{from: 31, to: 60, color: '#44B100', name: 'low'},
{from: 61, to: 90, color: '#88C100', name: 'low-medium'},
{from: 91, to: 120, color: '#B4D200', name: 'medium'},
{from: 121, to: 150, color: '#E0E300', name: 'medium-high'},
{from: 151, to: 180, color: '#FFB200', name: 'high'},
{from: 181, to: 210, color: '#FF8000', name: 'very high'},
{from: 211, to: 240, color: '#FF5C00', name: 'extremely high'},
{from: 241, to: 270, color: '#FF3200', name: 'critical'},
{from: 271, to: 300, color: '#FF0000', name: 'max'}
]
}
}
}
}
series: Object[] = []
isAuthorized() {
this.authorized = this.sessionStore.isAuthorized
}
mounted() {
this.isAuthorized()
this.sessionStore.$subscribe((mutation, state) => {
this.authorized = state.authorized
});
this.syncStore.$subscribe(() => {
console.debug(`Got sync store change - reloading component data`)
this.refreshHeatmap()
})
}
refreshHeatmap() {
let self = this
self.items = []
self.series = []
self.loadedData = false
const statType = HeatmapType.ARCHIVED === this.type ? HeatmapType.ARCHIVED : HeatmapType.TODO
this.statsService.getHeatmapOfArchived(statType)
.then((responseData: IActivityHeatmapStats) => {
self.items = responseData.items
self.prepareChatSeries()
self.loadedData = true
});
}
prepareChatSeries() {
type WeekdayData = { name: string, data: Array<{ x: string, y: number }> };
const weekdays: WeekdayData[] = [
{name: "Monday", data: []}, //0
{name: "Tuesday", data: []}, // 1
{name: "Wednesday", data: []},
{name: "Thursday", data: []},
{name: "Friday", data: []},
{name: "Saturday", data: []},
{name: "Sunday", data: []}, //6
]
const diffUserZoneToUTC = this.utcToUserTimeZoneDifferenceInHours()
// Iterate through the input data and populate the weekdays object
this.items.forEach((item: IActivityHeatmapItem) => {
let hour = (item.hour + diffUserZoneToUTC) % 24;
weekdays[item.weekday - 1]!.data.push({x: `${hour}`, y: item.count});
})
// Sort the data arrays for each weekday by x in ascending order
weekdays.forEach((weekday) => {
weekday.data = weekday.data.sort((a, b) => {
const aX = parseInt(a.x)
const bX = parseInt(b.x)
return aX - bX
});
});
this.series = Object.values(weekdays);
}
utcToUserTimeZoneDifferenceInHours() {
Intl.DateTimeFormat().resolvedOptions().timeZone
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const offsetMinutes = new Date().getTimezoneOffset();
const diff = -offsetMinutes / 60;
console.log(`Timezone: ${timeZone}, Difference to UTC in hours: ${diff}`);
return diff
}
}
</script>
2 changes: 1 addition & 1 deletion src/frontend/src/components/StatsPerPeriod.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</thead>
<tbody>

<tr v-for="periodStats in itemsStats" v-bind:key="periodStats.nameShort">
<tr v-for="periodStats in itemsStats" v-bind:key="periodStats.nameShort">
<td>{{ periodStats.nameDesc }}</td>
<td>
<span
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/components/TopAuth.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import {Vue} from "vue-class-component";
import {useSessionStore} from "@/store";
import {AuthorizationService} from "@/services/authorization-service";
export default class TopAuth extends Vue {
waitingForLogin: Boolean = false
authorized: Boolean = false
Expand All @@ -42,7 +43,6 @@ export default class TopAuth extends Vue {
let self = this
authService.getLoginUrl().then((response) => {
console.debug("got login url" + response.data.data.link)
self.loginUrl = response.data.data.link
self.showLoginButton = true
});
Expand Down
2 changes: 2 additions & 0 deletions src/frontend/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import VueAxios from 'vue-axios'
import axios from 'axios'
import router from "@/router";
import {createPinia} from "pinia";
import VueApexCharts from "vue3-apexcharts";
import {BootstrapVue3, BToastPlugin} from "bootstrap-vue-3";

import 'bootstrap/dist/css/bootstrap.css'
Expand All @@ -26,6 +27,7 @@ app
.use(router)
.use(BootstrapVue3)
.use(BToastPlugin)
.use(VueApexCharts)
.component("font-awesome-icon", FontAwesomeIcon)
.mount('#app')

17 changes: 16 additions & 1 deletion src/frontend/src/models/stats-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,19 @@ export type PeriodItemsStats = {
export type TimePeriod = {
begin: string //ISO standard
end: string //ISO standard
}
}

export enum HeatmapType {
ARCHIVED = "ARCHIVED",
TODO = "TODO"
}

export interface IActivityHeatmapItem {
hour: number
weekday: number
count: number
}

export interface IActivityHeatmapStats {
items: IActivityHeatmapItem[],
}
17 changes: 16 additions & 1 deletion src/frontend/src/services/stats-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import {ConfigsService} from "@/services/configs-service";
import axios from "axios";
import {format} from "date-fns";
import {TimePeriod} from "@/models/time-models";
import {DayStatsType, ILangStats, ItemsStatsAggregated, ITopTags,} from "@/models/stats-models";
import {
DayStatsType,
HeatmapType,
IActivityHeatmapStats,
ILangStats,
ItemsStatsAggregated,
ITopTags,
} from "@/models/stats-models";

export class StatsService {
configsService = new ConfigsService();
Expand Down Expand Up @@ -41,4 +48,12 @@ export class StatsService {
return response.data.data as ItemsStatsAggregated
});
}

getHeatmapOfArchived(type: HeatmapType) {
return axios
.get(`${this.configsService.backendUrl()}/stats/heatmap?type=` + type)
.then((response) => {
return response.data.data as IActivityHeatmapStats
});
}
}
23 changes: 21 additions & 2 deletions src/frontend/src/views/HomeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,20 @@
</div>

<div class="row mt-2">
<div class="col">
<div class="col-md-6 col-12 col-lg-2 block">
<LangStats></LangStats>
</div>
<div class="col-md-6 col-12 col-lg-10 block">
<HeatmapArchivedChart :header="'Archivity Activity Heatmap'" :type="'ARCHIVED'"/>
</div>

<div class="col-md-6 col-12 col-lg-10 block">
<HeatmapArchivedChart :header="'Adding Activity Heatmap'" :type="'TODO'"/>
</div>
</div>

<div class="row mt-2">

</div>
</template>

Expand All @@ -34,14 +45,22 @@ import TopTools from "@/components/SyncPanel.vue";
import TopTags from "@/components/TopTags.vue";
import LangStats from "@/components/LangStats.vue";
import StatsPerPeriod from "@/components/StatsPerPeriod.vue";
import HeatmapArchivedChart from "@/components/HeatmapArchivedChart.vue";
import {HeatmapType} from "@/models/stats-models";
@Options({
computed: {
HeatmapType() {
return HeatmapType
}
},
components: {
LangStats,
TopTags,
DailyStatsChart,
TopTools,
StatsPerPeriod
StatsPerPeriod,
HeatmapArchivedChart
},
})
export default class HomeView extends Vue {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package eu.cybershu.pocketstats.pocket.api;

import javax.validation.constraints.NotNull;

public record ActivityHeatmapItem(
/* 0-23 */
int hour,
/* 1-monday, 7- sunday */
int weekday,
@NotNull long count) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package eu.cybershu.pocketstats.pocket.api;

import java.util.List;

public record ActivityHeatmapStats(
List<ActivityHeatmapItem> items
) {
}
Loading

0 comments on commit 3c2bf39

Please sign in to comment.