diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-05 21:08:51 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-04-05 21:08:51 +0300 |
commit | 9c05a84cac5e6519ef545b14ead8989719c6f612 (patch) | |
tree | e93937c87050f9f9b5603bfe9b7f8aca86e146c8 /app/assets | |
parent | d4e0452ed946ca0cf4dd0537675abeda7a4c0ffa (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
26 files changed, 592 insertions, 17 deletions
diff --git a/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue b/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue index 2733a59f62d..1a586bd1e91 100644 --- a/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue +++ b/app/assets/javascripts/alerts_settings/components/alert_mapping_builder.vue @@ -142,7 +142,7 @@ export default { {{ $options.i18n.columns.fallbackKeyTitle }} <gl-icon v-gl-tooltip - name="question" + name="question-o" class="gl-text-gray-500" :title="$options.i18n.fallbackTooltip" /> diff --git a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue index ca65665b9ed..24a776e1a29 100644 --- a/app/assets/javascripts/clusters/agents/components/activity_events_list.vue +++ b/app/assets/javascripts/clusters/agents/components/activity_events_list.vue @@ -164,7 +164,7 @@ export default { :href="$options.emptyHelpLink" :title="$options.i18n.emptyTooltip" :aria-label="$options.i18n.emptyTooltip" - ><gl-icon name="question" :size="14" + ><gl-icon name="question-o" :size="14" /></gl-link> </template> </gl-empty-state> diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue index dbe2119fadb..d7e98638a11 100644 --- a/app/assets/javascripts/clusters_list/components/agent_table.vue +++ b/app/assets/javascripts/clusters_list/components/agent_table.vue @@ -305,7 +305,7 @@ export default { :title="$options.i18n.defaultConfigTooltip" :aria-label="$options.i18n.defaultConfigTooltip" class="gl-vertical-align-middle" - ><gl-icon name="question" :size="14" /></gl-link + ><gl-icon name="question-o" :size="14" /></gl-link ></span> </span> </template> diff --git a/app/assets/javascripts/environments/components/deploy_board.vue b/app/assets/javascripts/environments/components/deploy_board.vue index 31bc462f0b9..b2843b79ba6 100644 --- a/app/assets/javascripts/environments/components/deploy_board.vue +++ b/app/assets/javascripts/environments/components/deploy_board.vue @@ -158,7 +158,7 @@ export default { >{{ instanceTitle }} ({{ instanceCount }})</span > <span ref="legend-icon" data-testid="legend-tooltip-target"> - <gl-icon class="gl-text-blue-500 gl-ml-2" name="question" /> + <gl-icon class="gl-text-blue-500 gl-ml-2" name="question-o" /> </span> <gl-tooltip :target="() => $refs['legend-icon']" boundary="#content-body"> <div class="deploy-board-legend gl-display-flex gl-flex-direction-column"> diff --git a/app/assets/javascripts/feature_flags/components/strategy.vue b/app/assets/javascripts/feature_flags/components/strategy.vue index 76a68624a63..564942b4d80 100644 --- a/app/assets/javascripts/feature_flags/components/strategy.vue +++ b/app/assets/javascripts/feature_flags/components/strategy.vue @@ -138,7 +138,7 @@ export default { <template #description> {{ $options.i18n.strategyTypeDescription }} <gl-link :href="strategyTypeDocsPagePath" target="_blank"> - <gl-icon name="question" /> + <gl-icon name="question-o" /> </gl-link> </template> <gl-form-select @@ -202,7 +202,7 @@ export default { {{ $options.i18n.environmentsSelectDescription }} </span> <gl-link :href="environmentsScopeDocsPath" target="_blank"> - <gl-icon name="question" /> + <gl-icon name="question-o" /> </gl-link> </div> </div> diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue index 2799ea1378e..d05aa960f01 100644 --- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue +++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue @@ -82,7 +82,7 @@ export default { {{ __('Commit Message') }} <div id="ide-commit-message-popover-container"> <span id="ide-commit-message-question" class="form-text text-muted gl-ml-3"> - <gl-icon name="question" /> + <gl-icon name="question-o" /> </span> <gl-popover target="ide-commit-message-question" diff --git a/app/assets/javascripts/ide/components/shared/commit_message_field.vue b/app/assets/javascripts/ide/components/shared/commit_message_field.vue index 7fca7429ad7..428cf7f55ac 100644 --- a/app/assets/javascripts/ide/components/shared/commit_message_field.vue +++ b/app/assets/javascripts/ide/components/shared/commit_message_field.vue @@ -82,7 +82,7 @@ export default { <div>{{ __('Commit Message') }}</div> <div id="commit-message-popover-container"> <span id="commit-message-question" class="gl-gray-700 gl-ml-3"> - <gl-icon name="question" /> + <gl-icon name="question-o" /> </span> <gl-popover target="commit-message-question" diff --git a/app/assets/javascripts/issues/constants.js b/app/assets/javascripts/issues/constants.js index b7d885ed8a7..d35355a8f26 100644 --- a/app/assets/javascripts/issues/constants.js +++ b/app/assets/javascripts/issues/constants.js @@ -5,6 +5,7 @@ export const STATUS_CLOSED = 'closed'; export const STATUS_MERGED = 'merged'; export const STATUS_OPEN = 'opened'; export const STATUS_REOPENED = 'reopened'; +export const STATUS_LOCKED = 'locked'; export const TITLE_LENGTH_MAX = 255; @@ -22,4 +23,6 @@ export const IssuableStatusText = { [STATUS_CLOSED]: __('Closed'), [STATUS_OPEN]: __('Open'), [STATUS_REOPENED]: __('Open'), + [STATUS_MERGED]: __('Merged'), + [STATUS_LOCKED]: __('Open'), }; diff --git a/app/assets/javascripts/jobs/components/job/sidebar/artifacts_block.vue b/app/assets/javascripts/jobs/components/job/sidebar/artifacts_block.vue index 14aaaa219e9..1c7ba1d331b 100644 --- a/app/assets/javascripts/jobs/components/job/sidebar/artifacts_block.vue +++ b/app/assets/javascripts/jobs/components/job/sidebar/artifacts_block.vue @@ -55,7 +55,7 @@ export default { rel="noopener noreferrer nofollow" data-testid="artifact-expired-help-link" > - <gl-icon name="question" /> + <gl-icon name="question-o" /> </gl-link> </p> <p v-else-if="isLocked" class="build-detail-row"> diff --git a/app/assets/javascripts/lib/utils/datetime/time_spent_utility.js b/app/assets/javascripts/lib/utils/datetime/time_spent_utility.js new file mode 100644 index 00000000000..64c77bf1080 --- /dev/null +++ b/app/assets/javascripts/lib/utils/datetime/time_spent_utility.js @@ -0,0 +1,13 @@ +import { stringifyTime, parseSeconds } from './date_format_utility'; + +/** + * Formats seconds into a human readable value of elapsed time, + * optionally limiting it to hours. + * @param {Number} seconds Seconds to format + * @param {Boolean} limitToHours Whether or not to limit the elapsed time to be expressed in hours + * @return {String} Provided seconds in human readable elapsed time format + */ +export const formatTimeSpent = (seconds, limitToHours) => { + const negative = seconds < 0; + return (negative ? '- ' : '') + stringifyTime(parseSeconds(seconds, { limitToHours })); +}; diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index c1081239544..f9a70371680 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -2,3 +2,4 @@ export * from './datetime/timeago_utility'; export * from './datetime/date_format_utility'; export * from './datetime/date_calculation_utility'; export * from './datetime/pikaday_utility'; +export * from './datetime/time_spent_utility'; diff --git a/app/assets/javascripts/notes/mixins/discussion_navigation.js b/app/assets/javascripts/notes/mixins/discussion_navigation.js index 3dbcf28d11c..90de7db8c1b 100644 --- a/app/assets/javascripts/notes/mixins/discussion_navigation.js +++ b/app/assets/javascripts/notes/mixins/discussion_navigation.js @@ -63,6 +63,7 @@ function getPreviousDiscussion() { function handleJumpForBothPages(getDiscussion, ctx, fn, scrollOptions) { const discussion = getDiscussion(); + if (!isOverviewPage() && !discussion) { window.mrTabs?.eventHub.$once('NotesAppReady', () => { handleJumpForBothPages(getDiscussion, ctx, fn, scrollOptions); @@ -71,9 +72,12 @@ function handleJumpForBothPages(getDiscussion, ctx, fn, scrollOptions) { window.mrTabs?.tabShown('show', undefined, false); return; } - const id = discussion.dataset.discussionId; - ctx.expandDiscussion({ discussionId: id }); - scrollToElement(discussion, scrollOptions); + + if (discussion) { + const id = discussion.dataset.discussionId; + ctx.expandDiscussion({ discussionId: id }); + scrollToElement(discussion, scrollOptions); + } } export default { diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue index 242c5a1a97b..eab4be4dcf1 100644 --- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue +++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue @@ -176,7 +176,7 @@ export default { <gl-icon v-if="showDailyLimitMessage(option)" v-gl-tooltip.hover - name="question" + name="question-o" :title="scheduleDailyLimitMsg" /> </gl-form-radio> diff --git a/app/assets/javascripts/pages/time_tracking/timelogs/index.js b/app/assets/javascripts/pages/time_tracking/timelogs/index.js new file mode 100644 index 00000000000..41c78fbe3a6 --- /dev/null +++ b/app/assets/javascripts/pages/time_tracking/timelogs/index.js @@ -0,0 +1,3 @@ +import initTimelogsApp from '~/time_tracking'; + +initTimelogsApp(); diff --git a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js index 71f7e5b42f4..34e2763a478 100644 --- a/app/assets/javascripts/performance_bar/stores/performance_bar_store.js +++ b/app/assets/javascripts/performance_bar/stores/performance_bar_store.js @@ -1,4 +1,5 @@ import { mergeUrlParams } from '~/lib/utils/url_utility'; +import { __ } from '~/locale'; export default class PerformanceBarStore { constructor() { @@ -6,7 +7,9 @@ export default class PerformanceBarStore { } addRequest(requestId, requestUrl, operationName, requestParams, methodVerb) { - if (!this.findRequest(requestId)) { + if (this.findRequest(requestId)) { + this.updateRequestBatchedQueriesCount(requestId); + } else { let displayName = ''; if (methodVerb) { @@ -25,12 +28,28 @@ export default class PerformanceBarStore { fullUrl: mergeUrlParams(requestParams, requestUrl), method: methodVerb, details: {}, + queriesInBatch: 1, // only for GraphQL displayName, }); } return this.requests; } + updateRequestBatchedQueriesCount(requestId) { + const existingRequest = this.findRequest(requestId); + existingRequest.queriesInBatch += 1; + + const oldDisplayName = existingRequest.displayName; + const regex = /\d+ queries batched/; + if (regex.test(oldDisplayName)) { + existingRequest.displayName = oldDisplayName.replace( + regex, + `${existingRequest.queriesInBatch} queries batched`, + ); + } else { + existingRequest.displayName += __(` [${existingRequest.queriesInBatch} queries batched]`); + } + } findRequest(requestId) { return this.requests.find((request) => request.id === requestId); diff --git a/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue b/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue index e6aa3be0371..24dd978585c 100644 --- a/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue +++ b/app/assets/javascripts/snippets/components/snippet_visibility_edit.vue @@ -42,7 +42,7 @@ export default { <label> {{ __('Visibility level') }} <gl-link v-if="helpLink" :href="helpLink" target="_blank" - ><gl-icon :size="12" name="question" + ><gl-icon :size="12" name="question-o" /></gl-link> </label> <gl-form-group id="visibility-level-setting" class="gl-mb-0"> diff --git a/app/assets/javascripts/time_tracking/components/queries/get_timelogs.query.graphql b/app/assets/javascripts/time_tracking/components/queries/get_timelogs.query.graphql new file mode 100644 index 00000000000..3ba0ab29530 --- /dev/null +++ b/app/assets/javascripts/time_tracking/components/queries/get_timelogs.query.graphql @@ -0,0 +1,69 @@ +#import "~/graphql_shared/fragments/page_info.fragment.graphql" + +query timeTrackingReport( + $startDate: Time + $endDate: Time + $projectId: ProjectID + $groupId: GroupID + $username: String + $first: Int + $last: Int + $before: String + $after: String +) { + timelogs( + startDate: $startDate + endDate: $endDate + projectId: $projectId + groupId: $groupId + username: $username + first: $first + last: $last + after: $after + before: $before + sort: SPENT_AT_DESC + ) { + count + totalSpentTime + nodes { + id + project { + id + webUrl + fullPath + nameWithNamespace + } + timeSpent + user { + id + name + username + avatarUrl + webPath + } + spentAt + note { + id + body + } + summary + issue { + id + title + webUrl + state + reference + } + mergeRequest { + id + title + webUrl + state + reference + } + } + pageInfo { + ...PageInfo + } + } +} diff --git a/app/assets/javascripts/time_tracking/components/timelog_source_cell.vue b/app/assets/javascripts/time_tracking/components/timelog_source_cell.vue new file mode 100644 index 00000000000..33b0ac4b58e --- /dev/null +++ b/app/assets/javascripts/time_tracking/components/timelog_source_cell.vue @@ -0,0 +1,50 @@ +<script> +import { GlLink } from '@gitlab/ui'; +import { IssuableStatusText } from '~/issues/constants'; + +export default { + components: { + GlLink, + }, + props: { + timelog: { + type: Object, + required: true, + }, + }, + computed: { + subject() { + const { issue, mergeRequest } = this.timelog; + return issue || mergeRequest; + }, + issuableStatus() { + return IssuableStatusText[this.subject.state]; + }, + issuableFullReference() { + return this.timelog.project.fullPath + this.subject.reference; + }, + }, +}; +</script> + +<template> + <div class="gl-display-flex gl-flex-direction-column gl-gap-2 gl-text-left!"> + <gl-link + :href="subject.webUrl" + class="gl-text-gray-900 gl-hover-text-gray-900 gl-font-weight-bold" + data-testid="title-container" + > + {{ subject.title }} + </gl-link> + <span> + <gl-link + :href="subject.webUrl" + class="gl-text-gray-900 gl-hover-text-gray-900" + data-testid="reference-container" + > + {{ issuableFullReference }} + </gl-link> + • <span data-testid="state-container">{{ issuableStatus }}</span> + </span> + </div> +</template> diff --git a/app/assets/javascripts/time_tracking/components/timelogs_app.vue b/app/assets/javascripts/time_tracking/components/timelogs_app.vue new file mode 100644 index 00000000000..2069e4a6722 --- /dev/null +++ b/app/assets/javascripts/time_tracking/components/timelogs_app.vue @@ -0,0 +1,229 @@ +<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> diff --git a/app/assets/javascripts/time_tracking/components/timelogs_table.vue b/app/assets/javascripts/time_tracking/components/timelogs_table.vue new file mode 100644 index 00000000000..b2efb44f56f --- /dev/null +++ b/app/assets/javascripts/time_tracking/components/timelogs_table.vue @@ -0,0 +1,105 @@ +<script> +import { GlTable } from '@gitlab/ui'; +import { formatDate, formatTimeSpent } from '~/lib/utils/datetime_utility'; +import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; +import { s__ } from '~/locale'; +import TimelogSourceCell from './timelog_source_cell.vue'; + +const TIME_DATE_FORMAT = 'mmmm d, yyyy, HH:MM ("UTC:" o)'; + +export default { + components: { + GlTable, + UserAvatarLink, + TimelogSourceCell, + }, + props: { + entries: { + type: Array, + required: true, + }, + limitToHours: { + type: Boolean, + required: false, + default: false, + }, + }, + data() { + return { + fields: [ + { + key: 'spentAt', + label: s__('TimeTrackingReport|Spent at'), + tdClass: 'gl-md-w-30', + }, + { + key: 'source', + label: s__('TimeTrackingReport|Source'), + }, + { + key: 'user', + label: s__('TimeTrackingReport|User'), + tdClass: 'gl-md-w-20', + }, + { + key: 'timeSpent', + label: s__('TimeTrackingReport|Time spent'), + tdClass: 'gl-md-w-15', + }, + { + key: 'summary', + label: s__('TimeTrackingReport|Summary'), + }, + ], + }; + }, + methods: { + formatDate(date) { + return formatDate(date, TIME_DATE_FORMAT); + }, + formatTimeSpent(seconds) { + return formatTimeSpent(seconds, this.limitToHours); + }, + extractTimelogSummary(timelog) { + const { note, summary } = timelog; + return note?.body || summary; + }, + }, +}; +</script> + +<template> + <gl-table :items="entries" :fields="fields" stacked="md" show-empty> + <template #cell(spentAt)="{ item: { spentAt } }"> + <div data-testid="date-container" class="gl-text-left!">{{ formatDate(spentAt) }}</div> + </template> + + <template #cell(source)="{ item }"> + <timelog-source-cell :timelog="item" /> + </template> + + <template #cell(user)="{ item: { user } }"> + <user-avatar-link + class="gl-display-flex gl-text-gray-900 gl-hover-text-gray-900" + :link-href="user.webPath" + :img-src="user.avatarUrl" + :img-size="16" + :img-alt="user.name" + :tooltip-text="user.name" + :username="user.name" + /> + </template> + + <template #cell(timeSpent)="{ item: { timeSpent } }"> + <div data-testid="time-spent-container" class="gl-text-left!"> + {{ formatTimeSpent(timeSpent) }} + </div> + </template> + + <template #cell(summary)="{ item }"> + <div data-testid="summary-container" class="gl-text-left!"> + {{ extractTimelogSummary(item) }} + </div> + </template> + </gl-table> +</template> diff --git a/app/assets/javascripts/time_tracking/index.js b/app/assets/javascripts/time_tracking/index.js new file mode 100644 index 00000000000..9cff01799d9 --- /dev/null +++ b/app/assets/javascripts/time_tracking/index.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createDefaultClient from '~/lib/graphql'; +import { parseBoolean } from '~/lib/utils/common_utils'; +import TimelogsApp from './components/timelogs_app.vue'; + +Vue.use(VueApollo); + +export default () => { + const el = document.getElementById('js-timelogs-app'); + if (!el) { + return false; + } + + const { limitToHours } = el.dataset; + + const apolloProvider = new VueApollo({ + defaultClient: createDefaultClient(), + }); + + return new Vue({ + el, + apolloProvider, + render(createElement) { + return createElement(TimelogsApp, { + props: { + limitToHours: parseBoolean(limitToHours), + }, + }); + }, + }); +}; diff --git a/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue b/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue index e97701d5991..beff3b4c0c3 100644 --- a/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue +++ b/app/assets/javascripts/usage_quotas/storage/components/project_storage_detail.vue @@ -90,7 +90,7 @@ export default { :aria-label="helpLinkAriaLabel(item.storageType.name)" :data-testid="`${item.storageType.id}-help-link`" > - <gl-icon name="question" :size="12" /> + <gl-icon name="question-o" :size="12" /> </gl-link> </p> <p class="gl-mb-0" :data-testid="`${item.storageType.id}-description`"> diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index dd9d2ce66cd..4c8e4eb5aa1 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -269,7 +269,7 @@ export default { <span v-safe-html:[$options.safeHtmlConfig]="statusHtml"></span> </div> <div v-if="user.bot && user.websiteUrl" class="gl-text-blue-500"> - <gl-icon name="question" /> + <gl-icon name="question-o" /> <gl-link data-testid="user-popover-bot-docs-link" :href="user.websiteUrl"> <gl-sprintf :message="$options.I18N_USER_LEARN"> <template #name>{{ user.name }}</template> diff --git a/app/assets/stylesheets/framework/source_editor.scss b/app/assets/stylesheets/framework/source_editor.scss index 046b8636f65..f1ee4c94942 100644 --- a/app/assets/stylesheets/framework/source_editor.scss +++ b/app/assets/stylesheets/framework/source_editor.scss @@ -41,6 +41,29 @@ } .monaco-editor.gl-source-editor { + // Fix unreadable headings in tooltips for syntax highlighting themes that don't match general theme + &.vs-dark .markdown-hover { + h1, + h2, + h3, + h4, + h5, + h6 { + color: $source-editor-hover-light-text-color; + } + } + + &.vs .markdown-hover { + h1, + h2, + h3, + h4, + h5, + h6 { + color: $source-editor-hover-dark-text-color; + } + } + .margin-view-overlays { .line-numbers { @include gl-display-flex; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 2743bba976c..30849ecfdee 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -921,6 +921,12 @@ Board Swimlanes */ $board-swimlanes-headers-height: 64px; +/* +Source Editor theme overrides +*/ +$source-editor-hover-light-text-color: #ececef; +$source-editor-hover-dark-text-color: #333238; + /** Bootstrap 4.2.0 introduced new icons for validating forms. Our design system does not use those, so we are disabling them for now: diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index fc5be72f7cf..66c543aa654 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -117,3 +117,21 @@ margin-bottom: $gl-spacing-scale-5; } } + +.gl-md-w-15 { + @include gl-media-breakpoint-up(md) { + width: $gl-spacing-scale-15; + } +} + +.gl-md-w-20 { + @include gl-media-breakpoint-up(md) { + width: $gl-spacing-scale-20; + } +} + +.gl-md-w-30 { + @include gl-media-breakpoint-up(md) { + width: $gl-spacing-scale-30; + } +} |