code.gitea.io/gitea@v1.22.3/web_src/js/components/RepoCodeFrequency.vue (about)

     1  <script>
     2  import {SvgIcon} from '../svg.js';
     3  import {
     4    Chart,
     5    Legend,
     6    LinearScale,
     7    TimeScale,
     8    PointElement,
     9    LineElement,
    10    Filler,
    11  } from 'chart.js';
    12  import {GET} from '../modules/fetch.js';
    13  import {Line as ChartLine} from 'vue-chartjs';
    14  import {
    15    startDaysBetween,
    16    firstStartDateAfterDate,
    17    fillEmptyStartDaysWithZeroes,
    18  } from '../utils/time.js';
    19  import {chartJsColors} from '../utils/color.js';
    20  import {sleep} from '../utils.js';
    21  import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';
    22  
    23  const {pageData} = window.config;
    24  
    25  Chart.defaults.color = chartJsColors.text;
    26  Chart.defaults.borderColor = chartJsColors.border;
    27  
    28  Chart.register(
    29    TimeScale,
    30    LinearScale,
    31    Legend,
    32    PointElement,
    33    LineElement,
    34    Filler,
    35  );
    36  
    37  export default {
    38    components: {ChartLine, SvgIcon},
    39    props: {
    40      locale: {
    41        type: Object,
    42        required: true,
    43      },
    44    },
    45    data: () => ({
    46      isLoading: false,
    47      errorText: '',
    48      repoLink: pageData.repoLink || [],
    49      data: [],
    50    }),
    51    mounted() {
    52      this.fetchGraphData();
    53    },
    54    methods: {
    55      async fetchGraphData() {
    56        this.isLoading = true;
    57        try {
    58          let response;
    59          do {
    60            response = await GET(`${this.repoLink}/activity/code-frequency/data`);
    61            if (response.status === 202) {
    62              await sleep(1000); // wait for 1 second before retrying
    63            }
    64          } while (response.status === 202);
    65          if (response.ok) {
    66            this.data = await response.json();
    67            const weekValues = Object.values(this.data);
    68            const start = weekValues[0].week;
    69            const end = firstStartDateAfterDate(new Date());
    70            const startDays = startDaysBetween(start, end);
    71            this.data = fillEmptyStartDaysWithZeroes(startDays, this.data);
    72            this.errorText = '';
    73          } else {
    74            this.errorText = response.statusText;
    75          }
    76        } catch (err) {
    77          this.errorText = err.message;
    78        } finally {
    79          this.isLoading = false;
    80        }
    81      },
    82  
    83      toGraphData(data) {
    84        return {
    85          datasets: [
    86            {
    87              data: data.map((i) => ({x: i.week, y: i.additions})),
    88              pointRadius: 0,
    89              pointHitRadius: 0,
    90              fill: true,
    91              label: 'Additions',
    92              backgroundColor: chartJsColors['additions'],
    93              borderWidth: 0,
    94              tension: 0.3,
    95            },
    96            {
    97              data: data.map((i) => ({x: i.week, y: -i.deletions})),
    98              pointRadius: 0,
    99              pointHitRadius: 0,
   100              fill: true,
   101              label: 'Deletions',
   102              backgroundColor: chartJsColors['deletions'],
   103              borderWidth: 0,
   104              tension: 0.3,
   105            },
   106          ],
   107        };
   108      },
   109  
   110      getOptions() {
   111        return {
   112          responsive: true,
   113          maintainAspectRatio: false,
   114          animation: true,
   115          plugins: {
   116            legend: {
   117              display: true,
   118            },
   119          },
   120          scales: {
   121            x: {
   122              type: 'time',
   123              grid: {
   124                display: false,
   125              },
   126              time: {
   127                minUnit: 'month',
   128              },
   129              ticks: {
   130                maxRotation: 0,
   131                maxTicksLimit: 12,
   132              },
   133            },
   134            y: {
   135              ticks: {
   136                maxTicksLimit: 6,
   137              },
   138            },
   139          },
   140        };
   141      },
   142    },
   143  };
   144  </script>
   145  <template>
   146    <div>
   147      <div class="ui header tw-flex tw-items-center tw-justify-between">
   148        {{ isLoading ? locale.loadingTitle : errorText ? locale.loadingTitleFailed: `Code frequency over the history of ${repoLink.slice(1)}` }}
   149      </div>
   150      <div class="tw-flex ui segment main-graph">
   151        <div v-if="isLoading || errorText !== ''" class="gt-tc tw-m-auto">
   152          <div v-if="isLoading">
   153            <SvgIcon name="octicon-sync" class="tw-mr-2 job-status-rotate"/>
   154            {{ locale.loadingInfo }}
   155          </div>
   156          <div v-else class="text red">
   157            <SvgIcon name="octicon-x-circle-fill"/>
   158            {{ errorText }}
   159          </div>
   160        </div>
   161        <ChartLine
   162          v-memo="data" v-if="data.length !== 0"
   163          :data="toGraphData(data)" :options="getOptions()"
   164        />
   165      </div>
   166    </div>
   167  </template>
   168  <style scoped>
   169  .main-graph {
   170    height: 440px;
   171  }
   172  </style>