229 lines
5.9 KiB
Vue
229 lines
5.9 KiB
Vue
<script>
|
|
import * as Sentry from '@sentry/browser';
|
|
import {
|
|
GlButton,
|
|
GlFormGroup,
|
|
GlFormInput,
|
|
GlLoadingIcon,
|
|
GlKeysetPagination,
|
|
GlDatepicker,
|
|
} from '@gitlab/ui';
|
|
import { createAlert } from '~/alert';
|
|
import { formatTimeSpent } from '~/lib/utils/datetime_utility';
|
|
import { s__ } from '~/locale';
|
|
import getTimelogsQuery from './queries/get_timelogs.query.graphql';
|
|
import TimelogsTable from './timelogs_table.vue';
|
|
|
|
const ENTRIES_PER_PAGE = 20;
|
|
|
|
// Define initial dates to current date and time
|
|
const INITIAL_TO_DATE = new Date();
|
|
const INITIAL_FROM_DATE = new Date();
|
|
|
|
// Set the initial 'from' date to 30 days before the current date
|
|
INITIAL_FROM_DATE.setDate(INITIAL_TO_DATE.getDate() - 30);
|
|
|
|
export default {
|
|
components: {
|
|
GlButton,
|
|
GlFormGroup,
|
|
GlFormInput,
|
|
GlLoadingIcon,
|
|
GlKeysetPagination,
|
|
GlDatepicker,
|
|
TimelogsTable,
|
|
},
|
|
props: {
|
|
limitToHours: {
|
|
type: Boolean,
|
|
required: false,
|
|
default: false,
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
projectId: null,
|
|
groupId: null,
|
|
username: null,
|
|
timeSpentFrom: INITIAL_FROM_DATE,
|
|
timeSpentTo: INITIAL_TO_DATE,
|
|
cursor: {
|
|
first: ENTRIES_PER_PAGE,
|
|
after: null,
|
|
last: null,
|
|
before: null,
|
|
},
|
|
queryVariables: {
|
|
startDate: INITIAL_FROM_DATE,
|
|
endDate: INITIAL_TO_DATE,
|
|
projectId: null,
|
|
groupId: null,
|
|
username: null,
|
|
},
|
|
pageInfo: {},
|
|
report: [],
|
|
totalSpentTime: 0,
|
|
};
|
|
},
|
|
apollo: {
|
|
report: {
|
|
query: getTimelogsQuery,
|
|
variables() {
|
|
return {
|
|
...this.queryVariables,
|
|
...this.cursor,
|
|
};
|
|
},
|
|
update({ timelogs: { nodes = [], pageInfo = {}, totalSpentTime = 0 } = {} }) {
|
|
this.pageInfo = pageInfo;
|
|
this.totalSpentTime = totalSpentTime;
|
|
return nodes;
|
|
},
|
|
error(error) {
|
|
createAlert({ message: s__('TimeTrackingReport|Something went wrong. Please try again.') });
|
|
Sentry.captureException(error);
|
|
},
|
|
},
|
|
},
|
|
computed: {
|
|
isLoading() {
|
|
return this.$apollo.queries.report.loading;
|
|
},
|
|
showPagination() {
|
|
return this.pageInfo?.hasPreviousPage || this.pageInfo?.hasNextPage;
|
|
},
|
|
formattedTotalSpentTime() {
|
|
return formatTimeSpent(this.totalSpentTime, this.limitToHours);
|
|
},
|
|
},
|
|
methods: {
|
|
nullIfBlank(value) {
|
|
return value === '' ? null : value;
|
|
},
|
|
runReport() {
|
|
this.cursor = {
|
|
first: ENTRIES_PER_PAGE,
|
|
after: null,
|
|
last: null,
|
|
before: null,
|
|
};
|
|
|
|
this.queryVariables = {
|
|
startDate: this.nullIfBlank(this.timeSpentFrom),
|
|
endDate: this.nullIfBlank(this.timeSpentTo),
|
|
projectId: this.nullIfBlank(this.projectId),
|
|
groupId: this.nullIfBlank(this.groupId),
|
|
username: this.nullIfBlank(this.username),
|
|
};
|
|
},
|
|
nextPage(item) {
|
|
this.cursor = {
|
|
first: ENTRIES_PER_PAGE,
|
|
after: item,
|
|
last: null,
|
|
before: null,
|
|
};
|
|
},
|
|
prevPage(item) {
|
|
this.cursor = {
|
|
first: null,
|
|
after: null,
|
|
last: ENTRIES_PER_PAGE,
|
|
before: item,
|
|
};
|
|
},
|
|
clearTimeSpentFromDate() {
|
|
this.timeSpentFrom = null;
|
|
},
|
|
clearTimeSpentToDate() {
|
|
this.timeSpentTo = null;
|
|
},
|
|
},
|
|
i18n: {
|
|
username: s__('TimeTrackingReport|Username'),
|
|
from: s__('TimeTrackingReport|From'),
|
|
to: s__('TimeTrackingReport|To'),
|
|
runReport: s__('TimeTrackingReport|Run report'),
|
|
totalTimeSpentText: s__('TimeTrackingReport|Total time spent: '),
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="gl-display-flex gl-flex-direction-column gl-gap-5 gl-mt-5">
|
|
<form
|
|
class="gl-display-flex gl-flex-direction-column gl-md-flex-direction-row gl-gap-3"
|
|
@submit.prevent="runReport"
|
|
>
|
|
<gl-form-group
|
|
:label="$options.i18n.username"
|
|
label-for="timelog-form-username"
|
|
class="gl-mb-0 gl-md-form-input-md gl-w-full"
|
|
>
|
|
<gl-form-input
|
|
id="timelog-form-username"
|
|
v-model="username"
|
|
data-testid="form-username"
|
|
class="gl-w-full"
|
|
/>
|
|
</gl-form-group>
|
|
<gl-form-group
|
|
key="time-spent-from"
|
|
:label="$options.i18n.from"
|
|
class="gl-mb-0 gl-md-form-input-md gl-w-full"
|
|
>
|
|
<gl-datepicker
|
|
v-model="timeSpentFrom"
|
|
:target="null"
|
|
show-clear-button
|
|
autocomplete="off"
|
|
data-testid="form-from-date"
|
|
class="gl-max-w-full!"
|
|
@clear="clearTimeSpentFromDate"
|
|
/>
|
|
</gl-form-group>
|
|
<gl-form-group
|
|
key="time-spent-to"
|
|
:label="$options.i18n.to"
|
|
class="gl-mb-0 gl-md-form-input-md gl-w-full"
|
|
>
|
|
<gl-datepicker
|
|
v-model="timeSpentTo"
|
|
:target="null"
|
|
show-clear-button
|
|
autocomplete="off"
|
|
data-testid="form-to-date"
|
|
class="gl-max-w-full!"
|
|
@clear="clearTimeSpentToDate"
|
|
/>
|
|
</gl-form-group>
|
|
<gl-button
|
|
class="gl-align-self-end gl-w-full gl-md-w-auto"
|
|
variant="confirm"
|
|
@click="runReport"
|
|
>{{ $options.i18n.runReport }}</gl-button
|
|
>
|
|
</form>
|
|
<div
|
|
v-if="!isLoading"
|
|
data-testid="table-container"
|
|
class="gl-display-flex gl-flex-direction-column"
|
|
>
|
|
<div v-if="report.length" class="gl-display-flex gl-gap-2 gl-border-t gl-py-4">
|
|
<span class="gl-font-weight-bold">{{ $options.i18n.totalTimeSpentText }}</span>
|
|
<span data-testid="total-time-spent-container">{{ formattedTotalSpentTime }}</span>
|
|
</div>
|
|
|
|
<timelogs-table :limit-to-hours="limitToHours" :entries="report" />
|
|
|
|
<gl-keyset-pagination
|
|
v-if="showPagination"
|
|
v-bind="pageInfo"
|
|
class="gl-mt-3 gl-align-self-center"
|
|
@prev="prevPage"
|
|
@next="nextPage"
|
|
/>
|
|
</div>
|
|
<gl-loading-icon v-else size="lg" class="gl-mt-5" />
|
|
</div>
|
|
</template>
|