diff options
Diffstat (limited to 'app/assets/javascripts/sidebar/components/time_tracking')
7 files changed, 260 insertions, 40 deletions
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/constants.js b/app/assets/javascripts/sidebar/components/time_tracking/constants.js new file mode 100644 index 00000000000..56e986e3b27 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/constants.js @@ -0,0 +1 @@ +export const CREATE_TIMELOG_MODAL_ID = 'create-timelog-modal'; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue b/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue new file mode 100644 index 00000000000..ec8e1ee9952 --- /dev/null +++ b/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue @@ -0,0 +1,227 @@ +<script> +import { + GlFormGroup, + GlFormInput, + GlDatepicker, + GlFormTextarea, + GlModal, + GlAlert, + GlLink, + GlSprintf, +} from '@gitlab/ui'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { formatDate } from '~/lib/utils/datetime_utility'; +import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants'; +import { joinPaths } from '~/lib/utils/url_utility'; +import { s__ } from '~/locale'; +import createTimelogMutation from '../../queries/create_timelog.mutation.graphql'; +import { CREATE_TIMELOG_MODAL_ID } from './constants'; + +export default { + components: { + GlDatepicker, + GlFormGroup, + GlFormInput, + GlFormTextarea, + GlModal, + GlAlert, + GlLink, + GlSprintf, + }, + inject: ['issuableType'], + props: { + issuableId: { + type: String, + required: true, + }, + }, + data() { + return { + timeSpent: '', + spentAt: null, + summary: '', + isLoading: false, + saveError: '', + }; + }, + computed: { + submitDisabled() { + return this.isLoading || this.timeSpent.length === 0; + }, + primaryProps() { + return { + text: s__('CreateTimelogForm|Save'), + attributes: [ + { + variant: 'confirm', + disabled: this.submitDisabled, + loading: this.isLoading, + }, + ], + }; + }, + cancelProps() { + return { + text: s__('CreateTimelogForm|Cancel'), + }; + }, + timeTrackingDocsPath() { + return joinPaths(gon.relative_url_root || '', '/help/user/project/time_tracking.md'); + }, + issuableTypeName() { + return this.isIssue() + ? s__('CreateTimelogForm|issue') + : s__('CreateTimelogForm|merge request'); + }, + }, + methods: { + resetModal() { + this.isLoading = false; + this.timeSpent = ''; + this.spentAt = null; + this.summary = ''; + this.saveError = ''; + }, + close() { + this.resetModal(); + this.$refs.modal.close(); + }, + registerTimeSpent(event) { + event.preventDefault(); + + if (this.timeSpent.length === 0) { + return; + } + + this.isLoading = true; + this.saveError = ''; + + this.$apollo + .mutate({ + mutation: createTimelogMutation, + variables: { + input: { + timeSpent: this.timeSpent, + spentAt: this.spentAt + ? formatDate(this.spentAt, 'isoDateTime') + : formatDate(Date.now(), 'isoDateTime'), + summary: this.summary, + issuableId: this.getIssuableId(), + }, + }, + }) + .then(({ data }) => { + if (data.timelogCreate?.errors.length) { + this.saveError = data.timelogCreate.errors[0].message || data.timelogCreate.errors[0]; + } else { + this.close(); + } + }) + .catch((error) => { + this.saveError = + error?.message || + s__('CreateTimelogForm|An error occurred while saving the time entry.'); + }) + .finally(() => { + this.isLoading = false; + }); + }, + isIssue() { + return this.issuableType === 'issue'; + }, + getGraphQLEntityType() { + return this.isIssue() ? TYPE_ISSUE : TYPE_MERGE_REQUEST; + }, + updateSpentAtDate(val) { + this.spentAt = val; + }, + getIssuableId() { + return convertToGraphQLId(this.getGraphQLEntityType(), this.issuableId); + }, + }, + CREATE_TIMELOG_MODAL_ID, +}; +</script> + +<template> + <gl-modal + ref="modal" + :title="s__('CreateTimelogForm|Add time entry')" + :modal-id="$options.CREATE_TIMELOG_MODAL_ID" + size="sm" + data-testid="create-timelog-modal" + :action-primary="primaryProps" + :action-cancel="cancelProps" + @primary="registerTimeSpent" + @cancel="close" + @close="close" + @hide="close" + > + <p data-testid="timetracking-docs-link"> + <gl-sprintf + :message=" + s__( + 'CreateTimelogForm|Track time spent on this %{issuableTypeNameStart}%{issuableTypeNameEnd}. %{timeTrackingDocsLinkStart}%{timeTrackingDocsLinkEnd}', + ) + " + > + <template #issuableTypeName>{{ issuableTypeName }}</template> + <template #timeTrackingDocsLink> + <gl-link :href="timeTrackingDocsPath" target="_blank">{{ + s__('CreateTimelogForm|How do I track and estimate time?') + }}</gl-link> + </template> + </gl-sprintf> + </p> + <form + class="gl-display-flex gl-flex-direction-column js-quick-submit" + @submit.prevent="registerTimeSpent" + > + <div class="gl-display-flex gl-gap-3"> + <gl-form-group + key="time-spent" + label-for="time-spent" + :label="s__(`CreateTimelogForm|Time spent`)" + :description="s__(`CreateTimelogForm|Example: 1h 30m`)" + > + <gl-form-input + id="time-spent" + ref="timeSpent" + v-model="timeSpent" + class="gl-form-input-sm" + autocomplete="off" + /> + </gl-form-group> + <gl-form-group + key="spent-at" + optional + label-for="spent-at" + :label="s__(`CreateTimelogForm|Spent at`)" + > + <gl-datepicker + :target="null" + :value="spentAt" + show-clear-button + autocomplete="off" + size="small" + @input="updateSpentAtDate" + @clear="updateSpentAtDate(null)" + /> + </gl-form-group> + </div> + <gl-form-group + :label="s__('CreateTimelogForm|Summary')" + optional + label-for="summary" + class="gl-mb-0" + > + <gl-form-textarea id="summary" v-model="summary" rows="3" :no-resize="true" /> + </gl-form-group> + <gl-alert v-if="saveError" variant="danger" class="gl-mt-5" :dismissible="false"> + {{ saveError }} + </gl-alert> + <!-- This is needed to have the quick-submit behaviour (with Ctrl + Enter or Cmd + Enter) --> + <input type="submit" hidden /> + </form> + </gl-modal> +</template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql b/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql deleted file mode 100644 index 6e916893b5a..00000000000 --- a/app/assets/javascripts/sidebar/components/time_tracking/graphql/mutations/delete_timelog.mutation.graphql +++ /dev/null @@ -1,17 +0,0 @@ -#import "~/graphql_shared/fragments/issue_time_tracking.fragment.graphql" -#import "~/graphql_shared/fragments/merge_request_time_tracking.fragment.graphql" - -mutation deleteTimelog($input: TimelogDeleteInput!) { - timelogDelete(input: $input) { - errors - timelog { - id - issue { - ...IssueTimeTrackingFragment - } - mergeRequest { - ...MergeRequestTimeTrackingFragment - } - } - } -} diff --git a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue index 91c15061fb9..6cd9596e43f 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/help_state.vue @@ -1,5 +1,6 @@ <script> -import { GlButton, GlSafeHtmlDirective } from '@gitlab/ui'; +import { GlButton } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; import { joinPaths } from '~/lib/utils/url_utility'; import { sprintf, s__ } from '~/locale'; @@ -9,7 +10,7 @@ export default { GlButton, }, directives: { - SafeHtml: GlSafeHtmlDirective, + SafeHtml, }, computed: { href() { diff --git a/app/assets/javascripts/sidebar/components/time_tracking/report.vue b/app/assets/javascripts/sidebar/components/time_tracking/report.vue index 124464088cf..6f4ced06ddf 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/report.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/report.vue @@ -5,8 +5,8 @@ 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 { __, s__ } from '~/locale'; -import { timelogQueries } from '~/sidebar/constants'; -import deleteTimelogMutation from './graphql/mutations/delete_timelog.mutation.graphql'; +import { timelogQueries } from '../../constants'; +import deleteTimelogMutation from '../../queries/delete_timelog.mutation.graphql'; const TIME_DATE_FORMAT = 'mmmm d, yyyy, HH:MM ("UTC:" o)'; diff --git a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue index 62b05421884..06adc048942 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/sidebar_time_tracking.vue @@ -30,6 +30,11 @@ export default { required: false, default: false, }, + canAddTimeEntries: { + type: Boolean, + required: false, + default: true, + }, }, mounted() { this.listenForQuickActions(); @@ -67,6 +72,7 @@ export default { :issuable-id="issuableId" :issuable-iid="issuableIid" :limit-to-hours="limitToHours" + :can-add-time-entries="canAddTimeEntries" /> </div> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue index 13981c477c6..b32836dc87d 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue @@ -9,15 +9,17 @@ import { GlTooltipDirective, } from '@gitlab/ui'; import { IssuableType } from '~/issues/constants'; +import { BV_SHOW_MODAL } from '~/lib/utils/constants'; import { s__, __ } from '~/locale'; -import { HOW_TO_TRACK_TIME, timeTrackingQueries } from '~/sidebar/constants'; +import { HOW_TO_TRACK_TIME, timeTrackingQueries } from '../../constants'; import eventHub from '../../event_hub'; import TimeTrackingCollapsedState from './collapsed_state.vue'; import TimeTrackingComparisonPane from './comparison_pane.vue'; -import TimeTrackingHelpState from './help_state.vue'; import TimeTrackingReport from './report.vue'; import TimeTrackingSpentOnlyPane from './spent_only_pane.vue'; +import { CREATE_TIMELOG_MODAL_ID } from './constants'; +import CreateTimelogForm from './create_timelog_form.vue'; export default { name: 'IssuableTimeTracker', @@ -34,8 +36,8 @@ export default { TimeTrackingCollapsedState, TimeTrackingSpentOnlyPane, TimeTrackingComparisonPane, - TimeTrackingHelpState, TimeTrackingReport, + CreateTimelogForm, }, directives: { GlModal: GlModalDirective, @@ -87,6 +89,11 @@ export default { default: true, required: false, }, + canAddTimeEntries: { + type: Boolean, + required: false, + default: true, + }, }, data() { return { @@ -192,12 +199,12 @@ export default { eventHub.$on('timeTracker:refresh', this.refresh); }, methods: { - toggleHelpState(show) { - this.showHelp = show; - }, refresh() { this.$apollo.queries.issuableTimeTracking.refetch(); }, + openRegisterTimeSpentModal() { + this.$root.$emit(BV_SHOW_MODAL, CREATE_TIMELOG_MODAL_ID); + }, }, }; </script> @@ -215,24 +222,21 @@ export default { :time-estimate-human-readable="humanTimeEstimate" /> <div - class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center gl-font-weight-bold gl-mr-3" + class="hide-collapsed gl-line-height-20 gl-text-gray-900 gl-display-flex gl-align-items-center gl-font-weight-bold" > {{ __('Time tracking') }} <gl-loading-icon v-if="isTimeTrackingInfoLoading" size="sm" class="gl-ml-2" inline /> <gl-button - :data-testid="showHelpState ? 'closeHelpButton' : 'helpButton'" + v-if="canAddTimeEntries" + v-gl-tooltip.left category="tertiary" size="small" - variant="link" class="gl-ml-auto" - @click="toggleHelpState(!showHelpState)" + data-testid="add-time-entry-button" + :title="__('Add time entry')" + @click="openRegisterTimeSpentModal()" > - <gl-icon - v-gl-tooltip.left - :title="timeTrackingIconTitle" - :name="timeTrackingIconName" - class="gl-text-gray-900!" - /> + <gl-icon name="plus" class="gl-text-gray-900!" /> </gl-button> </div> <div v-if="!isTimeTrackingInfoLoading" class="hide-collapsed"> @@ -272,9 +276,7 @@ export default { <time-tracking-report :limit-to-hours="limitToHours" :issuable-id="issuableId" /> </gl-modal> </template> - <transition name="help-state-toggle"> - <time-tracking-help-state v-if="showHelpState" /> - </transition> + <create-timelog-form :issuable-id="issuableId" /> </div> </div> </template> |