diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-11 12:08:52 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-11 12:08:52 +0000 |
commit | 9f5ac379c76c278ee9ee1662e26c4612b0a117bd (patch) | |
tree | 49cd59544c083678fefd1e77340ca5e2b6e3565c /app/assets | |
parent | 7240fb1a06c9e1b254719426b1ac96ec2f00fe35 (diff) | |
download | gitlab-ce-9f5ac379c76c278ee9ee1662e26c4612b0a117bd.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
13 files changed, 218 insertions, 73 deletions
diff --git a/app/assets/javascripts/actioncable_connection_monitor.js b/app/assets/javascripts/actioncable_connection_monitor.js new file mode 100644 index 00000000000..fc4e436c7fb --- /dev/null +++ b/app/assets/javascripts/actioncable_connection_monitor.js @@ -0,0 +1,142 @@ +/* eslint-disable no-restricted-globals */ + +import { logger } from '@rails/actioncable'; + +// This is based on https://github.com/rails/rails/blob/5a477890c809d4a17dc0dede43c6b8cef81d8175/actioncable/app/javascript/action_cable/connection_monitor.js +// so that we can take advantage of the improved reconnection logic. We can remove this once we upgrade @rails/actioncable to a version that includes this. + +// Responsible for ensuring the cable connection is in good health by validating the heartbeat pings sent from the server, and attempting +// revival reconnections if things go astray. Internal class, not intended for direct user manipulation. + +const now = () => new Date().getTime(); + +const secondsSince = (time) => (now() - time) / 1000; +class ConnectionMonitor { + constructor(connection) { + this.visibilityDidChange = this.visibilityDidChange.bind(this); + this.connection = connection; + this.reconnectAttempts = 0; + } + + start() { + if (!this.isRunning()) { + this.startedAt = now(); + delete this.stoppedAt; + this.startPolling(); + addEventListener('visibilitychange', this.visibilityDidChange); + logger.log( + `ConnectionMonitor started. stale threshold = ${this.constructor.staleThreshold} s`, + ); + } + } + + stop() { + if (this.isRunning()) { + this.stoppedAt = now(); + this.stopPolling(); + removeEventListener('visibilitychange', this.visibilityDidChange); + logger.log('ConnectionMonitor stopped'); + } + } + + isRunning() { + return this.startedAt && !this.stoppedAt; + } + + recordPing() { + this.pingedAt = now(); + } + + recordConnect() { + this.reconnectAttempts = 0; + this.recordPing(); + delete this.disconnectedAt; + logger.log('ConnectionMonitor recorded connect'); + } + + recordDisconnect() { + this.disconnectedAt = now(); + logger.log('ConnectionMonitor recorded disconnect'); + } + + // Private + + startPolling() { + this.stopPolling(); + this.poll(); + } + + stopPolling() { + clearTimeout(this.pollTimeout); + } + + poll() { + this.pollTimeout = setTimeout(() => { + this.reconnectIfStale(); + this.poll(); + }, this.getPollInterval()); + } + + getPollInterval() { + const { staleThreshold, reconnectionBackoffRate } = this.constructor; + const backoff = (1 + reconnectionBackoffRate) ** Math.min(this.reconnectAttempts, 10); + const jitterMax = this.reconnectAttempts === 0 ? 1.0 : reconnectionBackoffRate; + const jitter = jitterMax * Math.random(); + return staleThreshold * 1000 * backoff * (1 + jitter); + } + + reconnectIfStale() { + if (this.connectionIsStale()) { + logger.log( + `ConnectionMonitor detected stale connection. reconnectAttempts = ${ + this.reconnectAttempts + }, time stale = ${secondsSince(this.refreshedAt)} s, stale threshold = ${ + this.constructor.staleThreshold + } s`, + ); + this.reconnectAttempts += 1; + if (this.disconnectedRecently()) { + logger.log( + `ConnectionMonitor skipping reopening recent disconnect. time disconnected = ${secondsSince( + this.disconnectedAt, + )} s`, + ); + } else { + logger.log('ConnectionMonitor reopening'); + this.connection.reopen(); + } + } + } + + get refreshedAt() { + return this.pingedAt ? this.pingedAt : this.startedAt; + } + + connectionIsStale() { + return secondsSince(this.refreshedAt) > this.constructor.staleThreshold; + } + + disconnectedRecently() { + return ( + this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold + ); + } + + visibilityDidChange() { + if (document.visibilityState === 'visible') { + setTimeout(() => { + if (this.connectionIsStale() || !this.connection.isOpen()) { + logger.log( + `ConnectionMonitor reopening stale connection on visibilitychange. visibilityState = ${document.visibilityState}`, + ); + this.connection.reopen(); + } + }, 200); + } + } +} + +ConnectionMonitor.staleThreshold = 6; // Server::Connections::BEAT_INTERVAL * 2 (missed two pings) +ConnectionMonitor.reconnectionBackoffRate = 0.15; + +export default ConnectionMonitor; diff --git a/app/assets/javascripts/actioncable_consumer.js b/app/assets/javascripts/actioncable_consumer.js index 5658ffc1a38..aeb61e61a3d 100644 --- a/app/assets/javascripts/actioncable_consumer.js +++ b/app/assets/javascripts/actioncable_consumer.js @@ -1,3 +1,10 @@ import { createConsumer } from '@rails/actioncable'; +import ConnectionMonitor from './actioncable_connection_monitor'; -export default createConsumer(); +const consumer = createConsumer(); + +if (consumer.connection) { + consumer.connection.monitor = new ConnectionMonitor(consumer.connection); +} + +export default consumer; diff --git a/app/assets/javascripts/admin/users/components/user_avatar.vue b/app/assets/javascripts/admin/users/components/user_avatar.vue index ff0e91fcb8f..ce22595609d 100644 --- a/app/assets/javascripts/admin/users/components/user_avatar.vue +++ b/app/assets/javascripts/admin/users/components/user_avatar.vue @@ -1,5 +1,5 @@ <script> -import { GlAvatarLink, GlAvatarLabeled, GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { GlAvatarLabeled, GlBadge, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { truncate } from '~/lib/utils/text_utility'; import { USER_AVATAR_SIZE, LENGTH_OF_USER_NOTE_TOOLTIP } from '../constants'; @@ -8,7 +8,6 @@ export default { GlTooltip: GlTooltipDirective, }, components: { - GlAvatarLink, GlAvatarLabeled, GlBadge, GlIcon, @@ -27,6 +26,11 @@ export default { adminUserHref() { return this.adminUserPath.replace('id', this.user.username); }, + adminUserMailto() { + // NOTE: 'mailto:' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives + // eslint-disable-next-line @gitlab/require-i18n-strings + return `mailto:${this.user.email}`; + }, userNoteShort() { return truncate(this.user.note, LENGTH_OF_USER_NOTE_TOOLTIP); }, @@ -36,10 +40,9 @@ export default { </script> <template> - <gl-avatar-link + <div v-if="user" - class="js-user-link" - :href="adminUserHref" + class="js-user-link gl-display-inline-block" :data-user-id="user.id" :data-username="user.username" > @@ -48,6 +51,8 @@ export default { :src="user.avatarUrl" :label="user.name" :sub-label="user.email" + :label-link="adminUserHref" + :sub-label-link="adminUserMailto" > <template #meta> <div v-if="user.note" class="gl-text-gray-500 gl-p-1"> @@ -60,5 +65,5 @@ export default { </div> </template> </gl-avatar-labeled> - </gl-avatar-link> + </div> </template> diff --git a/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue b/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue index ea68df9ce12..85fca589279 100644 --- a/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue +++ b/app/assets/javascripts/boards/components/board_add_new_column_trigger.vue @@ -13,8 +13,8 @@ export default { </script> <template> - <span class="gl-ml-4"> - <gl-button variant="success" @click="setAddColumnFormVisibility(true)" + <span class="gl-ml-3 gl-display-flex gl-align-items-center"> + <gl-button variant="confirm" @click="setAddColumnFormVisibility(true)" >{{ __('Create list') }} </gl-button> </span> diff --git a/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue index a2dbd52369f..64d02dbdc54 100644 --- a/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue +++ b/app/assets/javascripts/boards/components/sidebar/board_sidebar_milestone_select.vue @@ -10,7 +10,6 @@ import { } from '@gitlab/ui'; import BoardEditableItem from '~/boards/components/sidebar/board_editable_item.vue'; import createFlash from '~/flash'; -import { BV_DROPDOWN_HIDE } from '~/lib/utils/constants'; import { __, s__ } from '~/locale'; import projectMilestones from '../../graphql/project_milestones.query.graphql'; @@ -73,21 +72,20 @@ export default { return this.activeIssue.milestone?.title ?? this.$options.i18n.noMilestone; }, }, - mounted() { - this.$root.$on(BV_DROPDOWN_HIDE, () => { - this.$refs.sidebarItem.collapse(); - }); - }, methods: { ...mapActions(['setActiveIssueMilestone']), handleOpen() { this.edit = true; this.$refs.dropdown.show(); }, + handleClose() { + this.edit = false; + this.$refs.sidebarItem.collapse(); + }, async setMilestone(milestoneId) { this.loading = true; this.searchTitle = ''; - this.$refs.sidebarItem.collapse(); + this.handleClose(); try { const input = { milestoneId, projectPath: this.projectPath }; @@ -116,7 +114,7 @@ export default { :title="$options.i18n.milestone" :loading="loading" @open="handleOpen()" - @close="edit = false" + @close="handleClose" > <template v-if="hasMilestone" #collapsed> <strong class="gl-text-gray-900">{{ activeIssue.milestone.title }}</strong> @@ -126,6 +124,7 @@ export default { :text="dropdownText" :header-text="$options.i18n.assignMilestone" block + @hide="handleClose" > <gl-search-box-by-type ref="search" v-model.trim="searchTitle" class="gl-m-3" /> <gl-dropdown-item diff --git a/app/assets/javascripts/boards/components/toggle_focus.vue b/app/assets/javascripts/boards/components/toggle_focus.vue index 59ee47937c9..74805f8a681 100644 --- a/app/assets/javascripts/boards/components/toggle_focus.vue +++ b/app/assets/javascripts/boards/components/toggle_focus.vue @@ -1,11 +1,14 @@ <script> -import { GlIcon } from '@gitlab/ui'; +import { GlButton, GlTooltipDirective as GlTooltip } from '@gitlab/ui'; import { __ } from '~/locale'; import { hide } from '~/tooltips'; export default { components: { - GlIcon, + GlButton, + }, + directives: { + GlTooltip, }, props: { issueBoardsContentSelector: { @@ -35,18 +38,15 @@ export default { </script> <template> - <div class="board-extra-actions"> - <a + <div class="board-extra-actions gl-ml-3 gl-display-flex gl-align-items-center"> + <gl-button ref="toggleFocusModeButton" - href="#" - class="btn btn-default has-tooltip gl-ml-3 js-focus-mode-btn" + v-gl-tooltip + :icon="isFullscreen ? 'minimize' : 'maximize'" + class="js-focus-mode-btn" data-qa-selector="focus_mode_button" - role="button" - :aria-label="$options.i18n.toggleFocusMode" :title="$options.i18n.toggleFocusMode" @click="toggleFocusMode" - > - <gl-icon :name="isFullscreen ? 'minimize' : 'maximize'" /> - </a> + /> </div> </template> diff --git a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue index 84ce6674104..84883ead125 100644 --- a/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue +++ b/app/assets/javascripts/jobs/components/sidebar_job_details_container.vue @@ -95,7 +95,12 @@ export default { <p v-if="hasTags" class="build-detail-row" data-testid="job-tags"> <span class="font-weight-bold">{{ __('Tags:') }}</span> - <span v-for="(tag, i) in job.tags" :key="i" class="badge badge-primary mr-1">{{ tag }}</span> + <span + v-for="(tag, i) in job.tags" + :key="i" + class="badge badge-pill badge-primary gl-badge sm" + >{{ tag }}</span + > </p> </div> </template> diff --git a/app/assets/javascripts/label_manager.js b/app/assets/javascripts/label_manager.js index aa197edd449..2a020a66fd2 100644 --- a/app/assets/javascripts/label_manager.js +++ b/app/assets/javascripts/label_manager.js @@ -2,8 +2,7 @@ import $ from 'jquery'; import Sortable from 'sortablejs'; - -import { hide, dispose } from '~/tooltips'; +import { dispose } from '~/tooltips'; import { deprecatedCreateFlash as flash } from './flash'; import axios from './lib/utils/axios_utils'; import { __ } from './locale'; @@ -30,7 +29,6 @@ export default class LabelManager { } bindEvents() { - this.prioritizedLabels.find('.btn-action').on('mousedown', this, this.onButtonActionClick); return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); } @@ -46,11 +44,6 @@ export default class LabelManager { _this.toggleEmptyState($label, $btn, action); } - onButtonActionClick(e) { - e.stopPropagation(); - hide(e.currentTarget); - } - toggleEmptyState() { this.emptyState.classList.toggle( 'hidden', diff --git a/app/assets/javascripts/pages/projects/edit/index.js b/app/assets/javascripts/pages/projects/edit/index.js index 413e43c638b..16e7645592c 100644 --- a/app/assets/javascripts/pages/projects/edit/index.js +++ b/app/assets/javascripts/pages/projects/edit/index.js @@ -12,25 +12,23 @@ import initSearchSettings from '~/search_settings'; import initProjectPermissionsSettings from '../shared/permissions'; import initProjectLoadingSpinner from '../shared/save_project_loader'; -document.addEventListener('DOMContentLoaded', () => { - initFilePickers(); - initConfirmDangerModal(); - initSettingsPanels(); - initProjectDeleteButton(); - mountBadgeSettings(PROJECT_BADGE); +initFilePickers(); +initConfirmDangerModal(); +initSettingsPanels(); +initProjectDeleteButton(); +mountBadgeSettings(PROJECT_BADGE); - new UserCallout({ className: 'js-service-desk-callout' }); // eslint-disable-line no-new - initServiceDesk(); +new UserCallout({ className: 'js-service-desk-callout' }); // eslint-disable-line no-new +initServiceDesk(); - initProjectLoadingSpinner(); - initProjectPermissionsSettings(); - setupTransferEdit('.js-project-transfer-form', 'select.select2'); +initProjectLoadingSpinner(); +initProjectPermissionsSettings(); +setupTransferEdit('.js-project-transfer-form', 'select.select2'); - dirtySubmitFactory( - document.querySelectorAll( - '.js-general-settings-form, .js-mr-settings-form, .js-mr-approvals-form', - ), - ); +dirtySubmitFactory( + document.querySelectorAll( + '.js-general-settings-form, .js-mr-settings-form, .js-mr-approvals-form', + ), +); - initSearchSettings(); -}); +initSearchSettings(); diff --git a/app/assets/javascripts/pages/projects/environments/index/index.js b/app/assets/javascripts/pages/projects/environments/index/index.js index 4d5106f6d5f..554ed4f9786 100644 --- a/app/assets/javascripts/pages/projects/environments/index/index.js +++ b/app/assets/javascripts/pages/projects/environments/index/index.js @@ -1,3 +1,3 @@ import initEnvironments from '~/environments/'; -document.addEventListener('DOMContentLoaded', initEnvironments); +initEnvironments(); diff --git a/app/assets/javascripts/pages/projects/project_members/index.js b/app/assets/javascripts/pages/projects/project_members/index.js index f029b26fa78..2730e0f0b84 100644 --- a/app/assets/javascripts/pages/projects/project_members/index.js +++ b/app/assets/javascripts/pages/projects/project_members/index.js @@ -23,17 +23,15 @@ function mountRemoveMemberModal() { }); } -document.addEventListener('DOMContentLoaded', () => { - groupsSelect(); - memberExpirationDate(); - memberExpirationDate('.js-access-expiration-date-groups'); - mountRemoveMemberModal(); - initInviteMembersModal(); - initInviteMembersTrigger(); +groupsSelect(); +memberExpirationDate(); +memberExpirationDate('.js-access-expiration-date-groups'); +mountRemoveMemberModal(); +initInviteMembersModal(); +initInviteMembersTrigger(); - new Members(); // eslint-disable-line no-new - new UsersSelect(); // eslint-disable-line no-new -}); +new Members(); // eslint-disable-line no-new +new UsersSelect(); // eslint-disable-line no-new if (window.gon.features.vueProjectMembersList) { const SHARED_FIELDS = ['account', 'expires', 'maxRole', 'expiration', 'actions']; diff --git a/app/assets/javascripts/pages/projects/tags/show/index.js b/app/assets/javascripts/pages/projects/tags/show/index.js index 651cc05ca4f..6f5406f554f 100644 --- a/app/assets/javascripts/pages/projects/tags/show/index.js +++ b/app/assets/javascripts/pages/projects/tags/show/index.js @@ -1,10 +1,8 @@ import { redirectTo, getBaseURL, stripFinalUrlSegment } from '~/lib/utils/url_utility'; import { initRemoveTag } from '../remove_tag'; -document.addEventListener('DOMContentLoaded', () => { - initRemoveTag({ - onDelete: (path = '') => { - redirectTo(stripFinalUrlSegment([getBaseURL(), path].join(''))); - }, - }); +initRemoveTag({ + onDelete: (path = '') => { + redirectTo(stripFinalUrlSegment([getBaseURL(), path].join(''))); + }, }); diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue index 4a4cbbdaa70..de4bbb36141 100644 --- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue +++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue @@ -109,7 +109,7 @@ export default { <div v-for="(key, keyIndex) in keys" :key="key" - class="break-word gl-text-black-normal" + class="break-word" :class="{ 'mb-3 bold': keyIndex == 0 }" > {{ item[key] }} |