summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/sidebar/components/time_tracking/report.vue
blob: d4a8abb81a8c62f8020cc72e1252a0f6055b7c82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<script>
import { GlLoadingIcon, GlTable } from '@gitlab/ui';
import createFlash from '~/flash';
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { formatDate, parseSeconds, stringifyTime } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import { timelogQueries } from '~/sidebar/constants';

const TIME_DATE_FORMAT = 'mmmm d, yyyy, HH:MM ("UTC:" o)';

export default {
  components: {
    GlLoadingIcon,
    GlTable,
  },
  inject: ['issuableType'],
  props: {
    limitToHours: {
      type: Boolean,
      default: false,
      required: false,
    },
    issuableId: {
      type: String,
      required: true,
    },
  },
  data() {
    return { report: [], isLoading: true };
  },
  apollo: {
    report: {
      query() {
        return timelogQueries[this.issuableType].query;
      },
      variables() {
        return {
          id: convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId),
        };
      },
      update(data) {
        this.isLoading = false;
        return this.extractTimelogs(data);
      },
      error() {
        createFlash({ message: __('Something went wrong. Please try again.') });
      },
    },
  },
  methods: {
    isIssue() {
      return this.issuableType === 'issue';
    },
    getGraphQLEntityType() {
      return this.isIssue() ? TYPE_ISSUE : TYPE_MERGE_REQUEST;
    },
    extractTimelogs(data) {
      const timelogs = data?.issuable?.timelogs?.nodes || [];
      return timelogs.slice().sort((a, b) => new Date(a.spentAt) - new Date(b.spentAt));
    },
    formatDate(date) {
      return formatDate(date, TIME_DATE_FORMAT);
    },
    getSummary(summary, note) {
      return summary ?? note?.body;
    },
    getTotalTimeSpent() {
      const seconds = this.report.reduce((acc, item) => acc + item.timeSpent, 0);
      return this.formatTimeSpent(seconds);
    },
    formatTimeSpent(seconds) {
      const negative = seconds < 0;
      return (
        (negative ? '- ' : '') +
        stringifyTime(parseSeconds(seconds, { limitToHours: this.limitToHours }))
      );
    },
  },
  fields: [
    { key: 'spentAt', label: __('Spent At'), sortable: true },
    { key: 'user', label: __('User'), sortable: true },
    { key: 'timeSpent', label: __('Time Spent'), sortable: true },
    { key: 'summary', label: __('Summary / Note'), sortable: true },
  ],
};
</script>

<template>
  <div>
    <div v-if="isLoading"><gl-loading-icon size="md" /></div>
    <gl-table v-else :items="report" :fields="$options.fields" foot-clone>
      <template #cell(spentAt)="{ item: { spentAt } }">
        <div>{{ formatDate(spentAt) }}</div>
      </template>
      <template #foot(spentAt)>&nbsp;</template>

      <template #cell(user)="{ item: { user } }">
        <div>{{ user.name }}</div>
      </template>
      <template #foot(user)>&nbsp;</template>

      <template #cell(timeSpent)="{ item: { timeSpent } }">
        <div>{{ formatTimeSpent(timeSpent) }}</div>
      </template>
      <template #foot(timeSpent)>
        <div>{{ getTotalTimeSpent() }}</div>
      </template>

      <template #cell(summary)="{ item: { summary, note } }">
        <div>{{ getSummary(summary, note) }}</div>
      </template>
      <template #foot(note)>&nbsp;</template>
    </gl-table>
  </div>
</template>