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 index 813b47bb49bfd3fbad21a99ed0ae53226dfee983..9949a04a60067e61d09a75eae79abb3323996377 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/create_timelog_form.vue @@ -7,14 +7,13 @@ import { GlModal, GlAlert, GlLink, - GlSprintf, } from '@gitlab/ui'; import { convertToGraphQLId } from '~/graphql_shared/utils'; -import { TYPE_ISSUE } from '~/issues/constants'; +import { issuableTypeText, TYPE_ISSUE } from '~/issues/constants'; import { formatDate } from '~/lib/utils/datetime_utility'; import { TYPENAME_ISSUE, TYPENAME_MERGE_REQUEST } from '~/graphql_shared/constants'; import { joinPaths } from '~/lib/utils/url_utility'; -import { s__ } from '~/locale'; +import { s__, sprintf } from '~/locale'; import createTimelogMutation from '../../queries/create_timelog.mutation.graphql'; import { CREATE_TIMELOG_MODAL_ID } from './constants'; @@ -27,7 +26,6 @@ export default { GlModal, GlAlert, GlLink, - GlSprintf, }, inject: { issuableType: { @@ -50,6 +48,12 @@ export default { }; }, computed: { + modalText() { + const issuableTypeName = issuableTypeText[this.issuableType]; + return sprintf(s__('TimeTracking|Track time spent on this %{issuableTypeName}.'), { + issuableTypeName, + }); + }, submitDisabled() { return this.isLoading || this.timeSpent.length === 0; }, @@ -71,11 +75,6 @@ export default { 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() { @@ -129,11 +128,8 @@ export default { this.isLoading = false; }); }, - isIssue() { - return this.issuableType === TYPE_ISSUE; - }, getGraphQLEntityType() { - return this.isIssue() ? TYPENAME_ISSUE : TYPENAME_MERGE_REQUEST; + return this.issuableType === TYPE_ISSUE ? TYPENAME_ISSUE : TYPENAME_MERGE_REQUEST; }, updateSpentAtDate(val) { this.spentAt = val; @@ -160,21 +156,8 @@ export default { @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> + {{ modalText }} </p> <form class="gl-display-flex gl-flex-direction-column js-quick-submit" @@ -212,19 +195,17 @@ export default { /> </gl-form-group> </div> - <gl-form-group - :label="s__('CreateTimelogForm|Summary')" - optional - label-for="summary" - class="gl-mb-0" - > + <gl-form-group :label="s__('CreateTimelogForm|Summary')" optional label-for="summary"> <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"> + <gl-alert v-if="saveError" variant="danger" class="gl-mb-3" :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-link :href="timeTrackingDocsPath"> + {{ s__('CreateTimelogForm|How do I track and estimate time?') }} + </gl-link> </gl-modal> </template> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/report.vue b/app/assets/javascripts/sidebar/components/time_tracking/report.vue index 38dbb5379a9977fa3120a700bd8a0672bc09318d..2a05f630eed1ebc05038a31b33de5f92f647c4d8 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/report.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/report.vue @@ -21,7 +21,11 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, - inject: ['issuableType'], + inject: { + issuableType: { + default: null, + }, + }, props: { limitToHours: { type: Boolean, @@ -119,10 +123,10 @@ export default { }, }, fields: [ - { key: 'spentAt', label: __('Spent at'), tdClass: 'gl-w-quarter' }, - { key: 'user', label: __('User') }, + { key: 'spentAt', label: __('Date'), tdClass: 'gl-w-quarter' }, { key: 'timeSpent', label: __('Time spent'), tdClass: 'gl-w-15' }, - { key: 'summary', label: __('Summary / note') }, + { key: 'user', label: __('User') }, + { key: 'summary', label: __('Summary') }, { key: 'actions', label: '', tdClass: 'gl-w-10' }, ], }; @@ -137,11 +141,6 @@ export default { </template> <template #foot(spentAt)> </template> - <template #cell(user)="{ item: { user } }"> - <div>{{ user.name }}</div> - </template> - <template #foot(user)> </template> - <template #cell(timeSpent)="{ item: { timeSpent } }"> <div>{{ formatTimeSpent(timeSpent) }}</div> </template> @@ -149,6 +148,11 @@ export default { <div>{{ getTotalTimeSpent() }}</div> </template> + <template #cell(user)="{ item: { user } }"> + <div>{{ user.name }}</div> + </template> + <template #foot(user)> </template> + <template #cell(summary)="{ item: { summary, note } }"> <div>{{ getSummary(summary, note) }}</div> </template> @@ -165,9 +169,10 @@ export default { <div v-if="adminTimelog"> <gl-button v-gl-tooltip="{ title: deleteButtonTooltip }" - category="secondary" + category="tertiary" icon="remove" - data-testid="deleteButton" + variant="danger" + :aria-label="deleteButtonTooltip" :loading="isDeletingTimelog(id)" @click="deleteTimelog(id)" /> diff --git a/app/assets/javascripts/sidebar/components/time_tracking/set_time_estimate_form.vue b/app/assets/javascripts/sidebar/components/time_tracking/set_time_estimate_form.vue index 44c5896d6583630d9beb77eb2dc67aa5b5d54b93..c25be5c1cde8566d3070e5453181792c434fd645 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/set_time_estimate_form.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/set_time_estimate_form.vue @@ -1,7 +1,7 @@ <script> import { GlFormGroup, GlFormInput, GlModal, GlAlert, GlLink } from '@gitlab/ui'; import { helpPagePath } from '~/helpers/help_page_helper'; -import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants'; +import { issuableTypeText, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants'; import { s__, __, sprintf } from '~/locale'; import issueSetTimeEstimateMutation from '../../queries/issue_set_time_estimate.mutation.graphql'; import mergeRequestSetTimeEstimateMutation from '../../queries/merge_request_set_time_estimate.mutation.graphql'; @@ -20,7 +20,11 @@ export default { GlAlert, GlLink, }, - inject: ['issuableType'], + inject: { + issuableType: { + default: null, + }, + }, props: { fullPath: { type: String, @@ -44,7 +48,7 @@ export default { data() { return { currentEstimate: this.timeTracking.timeEstimate ?? 0, - timeEstimate: this.timeTracking.humanTimeEstimate ?? '0h', + timeEstimate: this.timeTracking.humanTimeEstimate ?? '', isSaving: false, isResetting: false, saveError: '', @@ -91,19 +95,17 @@ export default { ? s__('TimeTracking|Set time estimate') : s__('TimeTracking|Edit time estimate'); }, - isIssue() { - return this.issuableType === TYPE_ISSUE; - }, modalText() { + const issuableTypeName = issuableTypeText[this.issuableType || this.workItemType]; return sprintf(s__('TimeTracking|Set estimated time to complete this %{issuableTypeName}.'), { - issuableTypeName: this.isIssue ? __('issue') : __('merge request'), + issuableTypeName, }); }, }, watch: { timeTracking() { this.currentEstimate = this.timeTracking.timeEstimate ?? 0; - this.timeEstimate = this.timeTracking.humanTimeEstimate ?? '0h'; + this.timeEstimate = this.timeTracking.humanTimeEstimate ?? ''; }, }, methods: { @@ -181,12 +183,8 @@ export default { @secondary.prevent="resetTimeEstimate" @cancel="close" > - <p data-testid="timetracking-docs-link"> + <p> {{ modalText }} - - <gl-link :href="timeTrackingDocsPath">{{ - s__('TimeTracking|How do I estimate and track time?') - }}</gl-link> </p> <form class="js-quick-submit" @submit.prevent="saveTimeEstimate"> <gl-form-group @@ -205,11 +203,14 @@ export default { autocomplete="off" /> </gl-form-group> - <gl-alert v-if="saveError" variant="danger" class="gl-mt-5" :dismissible="false"> + <gl-alert v-if="saveError" variant="danger" class="gl-mb-3" :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-link :href="timeTrackingDocsPath"> + {{ s__('TimeTracking|How do I estimate and track time?') }} + </gl-link> </gl-modal> </template> diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1bc5865aaeefca72a98da4a4a20ecf2b717d0589..ac245a1230caee56d61566580818df85d0c7b0ca 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15280,15 +15280,6 @@ msgstr "" msgid "CreateTimelogForm|Time spent" msgstr "" -msgid "CreateTimelogForm|Track time spent on this %{issuableTypeNameStart}%{issuableTypeNameEnd}. %{timeTrackingDocsLinkStart}%{timeTrackingDocsLinkEnd}" -msgstr "" - -msgid "CreateTimelogForm|issue" -msgstr "" - -msgid "CreateTimelogForm|merge request" -msgstr "" - msgid "CreateValueStreamForm|%{name} (default)" msgstr "" @@ -49564,9 +49555,6 @@ msgstr "" msgid "Speed up your pipelines with Needs relationships" msgstr "" -msgid "Spent at" -msgstr "" - msgid "Spent at can't be a future date and time." msgstr "" @@ -50403,9 +50391,6 @@ msgstr "" msgid "Summary" msgstr "" -msgid "Summary / note" -msgstr "" - msgid "Summary comment (optional)" msgstr "" @@ -53253,6 +53238,9 @@ msgstr "" msgid "TimeTracking|To manage time, use /spend or /estimate." msgstr "" +msgid "TimeTracking|Track time spent on this %{issuableTypeName}." +msgstr "" + msgid "Timeago|%s days ago" msgstr "" diff --git a/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js b/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js index a3b32e9850683a4a2c1d89c63506f8c7a8a43e1d..ce076fd56c842ad1fb2507a14882227805448131 100644 --- a/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js +++ b/spec/frontend/sidebar/components/time_tracking/create_timelog_form_spec.js @@ -1,6 +1,6 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; -import { GlAlert, GlModal, GlFormInput, GlDatepicker, GlFormTextarea } from '@gitlab/ui'; +import { GlAlert, GlModal, GlFormInput, GlDatepicker, GlFormTextarea, GlLink } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; @@ -41,12 +41,11 @@ describe('Create Timelog Form', () => { Vue.use(VueApollo); let wrapper; - let fakeApollo; const findForm = () => wrapper.find('form'); const findModal = () => wrapper.findComponent(GlModal); const findAlert = () => wrapper.findComponent(GlAlert); - const findDocsLink = () => wrapper.findByTestId('timetracking-docs-link'); + const findDocsLink = () => wrapper.findComponent(GlLink); const findSaveButton = () => findModal().props('actionPrimary'); const findSaveButtonLoadingState = () => findSaveButton().attributes.loading; const findSaveButtonDisabledState = () => findSaveButton().attributes.disabled; @@ -60,8 +59,6 @@ describe('Create Timelog Form', () => { { props, providedProps } = {}, mutationResolverMock = rejectedMutationMock, ) => { - fakeApollo = createMockApollo([[createTimelogMutation, mutationResolverMock]]); - wrapper = shallowMountExtended(CreateTimelogForm, { provide: { issuableType: 'issue', @@ -71,7 +68,7 @@ describe('Create Timelog Form', () => { issuableId: '1', ...props, }, - apolloProvider: fakeApollo, + apolloProvider: createMockApollo([[createTimelogMutation, mutationResolverMock]]), stubs: { GlModal: stubComponent(GlModal, { methods: { close: modalCloseMock }, @@ -80,11 +77,6 @@ describe('Create Timelog Form', () => { }); }; - afterEach(() => { - fakeApollo = null; - modalCloseMock.mockClear(); - }); - describe('save button', () => { it('is disabled and not loading by default', () => { mountComponent(); @@ -227,7 +219,8 @@ describe('Create Timelog Form', () => { it('is present', () => { mountComponent(); - expect(findDocsLink().exists()).toBe(true); + expect(findDocsLink().text()).toBe('How do I track and estimate time?'); + expect(findDocsLink().attributes('href')).toBe('/help/user/project/time_tracking.md'); }); }); }); diff --git a/spec/frontend/sidebar/components/time_tracking/report_spec.js b/spec/frontend/sidebar/components/time_tracking/report_spec.js index 6f25c4a10fd6130df5c0a20f2c78af0b5a03ae3c..46104cff15827e1e77c02058b2ee9c883bcfeeec 100644 --- a/spec/frontend/sidebar/components/time_tracking/report_spec.js +++ b/spec/frontend/sidebar/components/time_tracking/report_spec.js @@ -1,5 +1,5 @@ -import { GlLoadingIcon } from '@gitlab/ui'; -import { getAllByRole, getByRole, getAllByTestId } from '@testing-library/dom'; +import { GlButton, GlLoadingIcon } from '@gitlab/ui'; +import { getAllByRole, getByRole } from '@testing-library/dom'; import { shallowMount, mount } from '@vue/test-utils'; import Vue from 'vue'; import VueApollo from 'vue-apollo'; @@ -25,7 +25,7 @@ describe('Issuable Time Tracking Report', () => { let wrapper; const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); - const findDeleteButton = () => wrapper.findByTestId('deleteButton'); + const findDeleteButton = () => wrapper.findComponent(GlButton); const successIssueQueryHandler = jest.fn().mockResolvedValue(getIssueTimelogsQueryResponse); const successMrQueryHandler = jest.fn().mockResolvedValue(getMrTimelogsQueryResponse); @@ -81,7 +81,9 @@ describe('Issuable Time Tracking Report', () => { expect(getAllByRole(wrapper.element, 'row', { name: /Administrator/i })).toHaveLength(2); expect(getAllByRole(wrapper.element, 'row', { name: /A note/i })).toHaveLength(1); expect(getAllByRole(wrapper.element, 'row', { name: /A summary/i })).toHaveLength(2); - expect(getAllByTestId(wrapper.element, 'deleteButton')).toHaveLength(1); + expect(getAllByRole(wrapper.element, 'button', { name: /Delete time spent/ })).toHaveLength( + 1, + ); }); }); @@ -102,7 +104,9 @@ describe('Issuable Time Tracking Report', () => { await waitForPromises(); expect(getAllByRole(wrapper.element, 'row', { name: /Administrator/i })).toHaveLength(3); - expect(getAllByTestId(wrapper.element, 'deleteButton')).toHaveLength(3); + expect(getAllByRole(wrapper.element, 'button', { name: /Delete time spent/ })).toHaveLength( + 3, + ); }); }); diff --git a/spec/frontend/sidebar/components/time_tracking/set_time_estimate_form_spec.js b/spec/frontend/sidebar/components/time_tracking/set_time_estimate_form_spec.js index 37d7b3b67818900083d5ae92fa8bd2d271eb596c..75c4f118764e318672cb748c501d7c5374f44aca 100644 --- a/spec/frontend/sidebar/components/time_tracking/set_time_estimate_form_spec.js +++ b/spec/frontend/sidebar/components/time_tracking/set_time_estimate_form_spec.js @@ -1,6 +1,6 @@ import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; -import { GlModal, GlAlert } from '@gitlab/ui'; +import { GlModal, GlAlert, GlLink } from '@gitlab/ui'; import setIssueTimeEstimateWithErrors from 'test_fixtures/graphql/issue_set_time_estimate_with_errors.json'; import setIssueTimeEstimateWithoutErrors from 'test_fixtures/graphql/issue_set_time_estimate_without_errors.json'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -35,7 +35,7 @@ describe('Set Time Estimate Form', () => { const findModal = () => wrapper.findComponent(GlModal); const findModalTitle = () => findModal().props('title'); const findAlert = () => wrapper.findComponent(GlAlert); - const findDocsLink = () => wrapper.findByTestId('timetracking-docs-link'); + const findDocsLink = () => wrapper.findComponent(GlLink); const findSaveButton = () => findModal().props('actionPrimary'); const findSaveButtonLoadingState = () => findSaveButton().attributes.loading; const findSaveButtonDisabledState = () => findSaveButton().attributes.disabled; @@ -414,7 +414,8 @@ describe('Set Time Estimate Form', () => { it('is present', async () => { await mountComponent(); - expect(findDocsLink().exists()).toBe(true); + expect(findDocsLink().text()).toBe('How do I estimate and track time?'); + expect(findDocsLink().attributes('href')).toBe('/help/user/project/time_tracking.md'); }); }); });