summaryrefslogtreecommitdiffstats
path: root/web_src/js/components/RepoCodeFrequency.vue
diff options
context:
space:
mode:
Diffstat (limited to 'web_src/js/components/RepoCodeFrequency.vue')
-rw-r--r--web_src/js/components/RepoCodeFrequency.vue172
1 files changed, 172 insertions, 0 deletions
diff --git a/web_src/js/components/RepoCodeFrequency.vue b/web_src/js/components/RepoCodeFrequency.vue
new file mode 100644
index 00000000..1d40d6d4
--- /dev/null
+++ b/web_src/js/components/RepoCodeFrequency.vue
@@ -0,0 +1,172 @@
+<script>
+import {SvgIcon} from '../svg.js';
+import {
+ Chart,
+ Legend,
+ LinearScale,
+ TimeScale,
+ PointElement,
+ LineElement,
+ Filler,
+} from 'chart.js';
+import {GET} from '../modules/fetch.js';
+import {Line as ChartLine} from 'vue-chartjs';
+import {
+ startDaysBetween,
+ firstStartDateAfterDate,
+ fillEmptyStartDaysWithZeroes,
+} from '../utils/time.js';
+import {chartJsColors} from '../utils/color.js';
+import {sleep} from '../utils.js';
+import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
+
+const {pageData} = window.config;
+
+Chart.defaults.color = chartJsColors.text;
+Chart.defaults.borderColor = chartJsColors.border;
+
+Chart.register(
+ TimeScale,
+ LinearScale,
+ Legend,
+ PointElement,
+ LineElement,
+ Filler,
+);
+
+export default {
+ components: {ChartLine, SvgIcon},
+ props: {
+ locale: {
+ type: Object,
+ required: true,
+ },
+ },
+ data: () => ({
+ isLoading: false,
+ errorText: '',
+ repoLink: pageData.repoLink || [],
+ data: [],
+ }),
+ mounted() {
+ this.fetchGraphData();
+ },
+ methods: {
+ async fetchGraphData() {
+ this.isLoading = true;
+ try {
+ let response;
+ do {
+ response = await GET(`${this.repoLink}/activity/code-frequency/data`);
+ if (response.status === 202) {
+ await sleep(1000); // wait for 1 second before retrying
+ }
+ } while (response.status === 202);
+ if (response.ok) {
+ this.data = await response.json();
+ const weekValues = Object.values(this.data);
+ const start = weekValues[0].week;
+ const end = firstStartDateAfterDate(new Date());
+ const startDays = startDaysBetween(start, end);
+ this.data = fillEmptyStartDaysWithZeroes(startDays, this.data);
+ this.errorText = '';
+ } else {
+ this.errorText = response.statusText;
+ }
+ } catch (err) {
+ this.errorText = err.message;
+ } finally {
+ this.isLoading = false;
+ }
+ },
+
+ toGraphData(data) {
+ return {
+ datasets: [
+ {
+ data: data.map((i) => ({x: i.week, y: i.additions})),
+ pointRadius: 0,
+ pointHitRadius: 0,
+ fill: true,
+ label: 'Additions',
+ backgroundColor: chartJsColors['additions'],
+ borderWidth: 0,
+ tension: 0.3,
+ },
+ {
+ data: data.map((i) => ({x: i.week, y: -i.deletions})),
+ pointRadius: 0,
+ pointHitRadius: 0,
+ fill: true,
+ label: 'Deletions',
+ backgroundColor: chartJsColors['deletions'],
+ borderWidth: 0,
+ tension: 0.3,
+ },
+ ],
+ };
+ },
+
+ getOptions() {
+ return {
+ responsive: true,
+ maintainAspectRatio: false,
+ animation: true,
+ plugins: {
+ legend: {
+ display: true,
+ },
+ },
+ scales: {
+ x: {
+ type: 'time',
+ grid: {
+ display: false,
+ },
+ time: {
+ minUnit: 'month',
+ },
+ ticks: {
+ maxRotation: 0,
+ maxTicksLimit: 12,
+ },
+ },
+ y: {
+ ticks: {
+ maxTicksLimit: 6,
+ },
+ },
+ },
+ };
+ },
+ },
+};
+</script>
+<template>
+ <div>
+ <div class="ui header tw-flex tw-items-center tw-justify-between">
+ {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: `Code frequency over the history of ${repoLink.slice(1)}` }}
+ </div>
+ <div class="tw-flex ui segment main-graph">
+ <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
+ <div v-if="isLoading">
+ <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/>
+ {{ locale.loadingInfo }}
+ </div>
+ <div v-else class="text red">
+ <SvgIcon name="octicon-x-circle-fill"/>
+ {{ errorText }}
+ </div>
+ </div>
+ <ChartLine
+ v-memo="data" v-if="data.length !== 0"
+ :data="toGraphData(data)" :options="getOptions()"
+ />
+ </div>
+ </div>
+</template>
+<style scoped>
+.main-graph {
+ height: 440px;
+}
+</style>