diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-29 15:11:22 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-09-29 15:11:22 +0300 |
commit | 5f8d4d631d241c993c2cac54db3494f474b21dc1 (patch) | |
tree | ad82966503efbc7d8dc37601ff498af85d76b495 /app/assets | |
parent | c724e639a91a4d112b7f0a05b3c6a0ffa6baa7a4 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
22 files changed, 294 insertions, 56 deletions
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js index a1911585f80..a548b283142 100644 --- a/app/assets/javascripts/behaviors/preview_markdown.js +++ b/app/assets/javascripts/behaviors/preview_markdown.js @@ -81,7 +81,7 @@ MarkdownPreview.prototype.fetchMarkdownPreview = function (text, url, success) { }) .catch(() => createFlash({ - message: __('An error occurred while fetching markdown preview'), + message: __('An error occurred while fetching Markdown preview'), }), ); }; diff --git a/app/assets/javascripts/cycle_analytics/index.js b/app/assets/javascripts/cycle_analytics/index.js index 620da0104e0..34ef03409b8 100644 --- a/app/assets/javascripts/cycle_analytics/index.js +++ b/app/assets/javascripts/cycle_analytics/index.js @@ -45,6 +45,7 @@ export default () => { new Vue({ el, name: 'CycleAnalytics', + apolloProvider: {}, store, render: (createElement) => createElement(CycleAnalytics, { diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue index 051ab710e5f..7acb5549273 100644 --- a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue +++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue @@ -25,7 +25,7 @@ export default { lazy: true, }, translations: { - cronPlaceholder: __('* * * * *'), + cronPlaceholder: '* * * * *', cronSyntaxInstructions: __( 'Define a custom deploy freeze pattern with %{cronSyntaxStart}cron syntax%{cronSyntaxEnd}', ), diff --git a/app/assets/javascripts/notifications/constants.js b/app/assets/javascripts/notifications/constants.js index 4f875977d78..f5891c9acb5 100644 --- a/app/assets/javascripts/notifications/constants.js +++ b/app/assets/javascripts/notifications/constants.js @@ -31,7 +31,7 @@ export const i18n = { title: __('Custom notification events'), bodyTitle: __('Notification events'), bodyMessage: __( - 'Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notificationLinkStart} notification emails%{notificationLinkEnd}.', + 'Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notificationLinkStart}notification emails%{notificationLinkEnd}.', ), }, eventNames: { diff --git a/app/assets/javascripts/packages_and_registries/settings/project/constants.js b/app/assets/javascripts/packages_and_registries/settings/project/constants.js index 165c4aae3cb..4d477fbd05d 100644 --- a/app/assets/javascripts/packages_and_registries/settings/project/constants.js +++ b/app/assets/javascripts/packages_and_registries/settings/project/constants.js @@ -73,6 +73,7 @@ export const OLDER_THAN_OPTIONS = [ { key: 'SEVEN_DAYS', variable: 7, default: false }, { key: 'FOURTEEN_DAYS', variable: 14, default: false }, { key: 'THIRTY_DAYS', variable: 30, default: false }, + { key: 'SIXTY_DAYS', variable: 60, default: false }, { key: 'NINETY_DAYS', variable: 90, default: true }, ]; diff --git a/app/assets/javascripts/pages/profiles/index.js b/app/assets/javascripts/pages/profiles/index.js index 80bc32dd43f..6afb3636998 100644 --- a/app/assets/javascripts/pages/profiles/index.js +++ b/app/assets/javascripts/pages/profiles/index.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import '~/profile/gl_crop'; import Profile from '~/profile/profile'; import initSearchSettings from '~/search_settings'; +import initPasswordPrompt from './password_prompt'; // eslint-disable-next-line func-names $(document).on('input.ssh_key', '#key_key', function () { @@ -19,3 +20,4 @@ $(document).on('input.ssh_key', '#key_key', function () { new Profile(); // eslint-disable-line no-new initSearchSettings(); +initPasswordPrompt(); diff --git a/app/assets/javascripts/pages/profiles/password_prompt/constants.js b/app/assets/javascripts/pages/profiles/password_prompt/constants.js new file mode 100644 index 00000000000..99b8442c928 --- /dev/null +++ b/app/assets/javascripts/pages/profiles/password_prompt/constants.js @@ -0,0 +1,9 @@ +import { __, s__ } from '~/locale'; + +export const I18N_PASSWORD_PROMPT_TITLE = s__('PasswordPrompt|Confirm password to continue'); +export const I18N_PASSWORD_PROMPT_FORM_LABEL = s__( + 'PasswordPrompt|Please enter your password to confirm', +); +export const I18N_PASSWORD_PROMPT_ERROR_MESSAGE = s__('PasswordPrompt|Password is required'); +export const I18N_PASSWORD_PROMPT_CONFIRM_BUTTON = s__('PasswordPrompt|Confirm password'); +export const I18N_PASSWORD_PROMPT_CANCEL_BUTTON = __('Cancel'); diff --git a/app/assets/javascripts/pages/profiles/password_prompt/index.js b/app/assets/javascripts/pages/profiles/password_prompt/index.js new file mode 100644 index 00000000000..20645112893 --- /dev/null +++ b/app/assets/javascripts/pages/profiles/password_prompt/index.js @@ -0,0 +1,58 @@ +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; +import PasswordPromptModal from './password_prompt_modal.vue'; + +Vue.use(Translate); + +const emailFieldSelector = '#user_email'; +const editFormSelector = '.js-password-prompt-form'; +const passwordPromptFieldSelector = '.js-password-prompt-field'; +const passwordPromptBtnSelector = '.js-password-prompt-btn'; + +const passwordPromptModalId = 'password-prompt-modal'; + +const getEmailValue = () => document.querySelector(emailFieldSelector).value.trim(); +const passwordPromptButton = document.querySelector(passwordPromptBtnSelector); +const field = document.querySelector(passwordPromptFieldSelector); +const form = document.querySelector(editFormSelector); + +const handleConfirmPassword = (pw) => { + // update the validation_password field + field.value = pw; + // submit the form + form.submit(); +}; + +export default () => { + const passwordPromptModalEl = document.getElementById(passwordPromptModalId); + + if (passwordPromptModalEl && field) { + return new Vue({ + el: passwordPromptModalEl, + data() { + return { + initialEmail: '', + }; + }, + mounted() { + this.initialEmail = getEmailValue(); + passwordPromptButton.addEventListener('click', this.handleSettingsUpdate); + }, + methods: { + handleSettingsUpdate(ev) { + const email = getEmailValue(); + if (email !== this.initialEmail) { + ev.preventDefault(); + this.$root.$emit('bv::show::modal', passwordPromptModalId, passwordPromptBtnSelector); + } + }, + }, + render(createElement) { + return createElement(PasswordPromptModal, { + props: { handleConfirmPassword }, + }); + }, + }); + } + return null; +}; diff --git a/app/assets/javascripts/pages/profiles/password_prompt/password_prompt_modal.vue b/app/assets/javascripts/pages/profiles/password_prompt/password_prompt_modal.vue new file mode 100644 index 00000000000..44728ea9cdf --- /dev/null +++ b/app/assets/javascripts/pages/profiles/password_prompt/password_prompt_modal.vue @@ -0,0 +1,82 @@ +<script> +import { GlModal, GlForm, GlFormGroup, GlFormInput } from '@gitlab/ui'; +import { + I18N_PASSWORD_PROMPT_TITLE, + I18N_PASSWORD_PROMPT_FORM_LABEL, + I18N_PASSWORD_PROMPT_ERROR_MESSAGE, + I18N_PASSWORD_PROMPT_CANCEL_BUTTON, + I18N_PASSWORD_PROMPT_CONFIRM_BUTTON, +} from './constants'; + +export default { + components: { + GlModal, + GlForm, + GlFormGroup, + GlFormInput, + }, + props: { + handleConfirmPassword: { + type: Function, + required: true, + }, + }, + data() { + return { + passwordCheck: '', + }; + }, + computed: { + isValid() { + return Boolean(this.passwordCheck.length); + }, + primaryProps() { + return { + text: I18N_PASSWORD_PROMPT_CONFIRM_BUTTON, + attributes: [{ variant: 'danger' }, { category: 'primary' }, { disabled: !this.isValid }], + }; + }, + }, + methods: { + onConfirmPassword() { + this.handleConfirmPassword(this.passwordCheck); + }, + }, + cancelProps: { + text: I18N_PASSWORD_PROMPT_CANCEL_BUTTON, + }, + i18n: { + title: I18N_PASSWORD_PROMPT_TITLE, + formLabel: I18N_PASSWORD_PROMPT_FORM_LABEL, + errorMessage: I18N_PASSWORD_PROMPT_ERROR_MESSAGE, + }, +}; +</script> + +<template> + <gl-modal + data-testid="password-prompt-modal" + modal-id="password-prompt-modal" + :title="$options.i18n.title" + :action-primary="primaryProps" + :action-cancel="$options.cancelProps" + @primary="onConfirmPassword" + > + <gl-form @submit.prevent="onConfirmPassword"> + <gl-form-group + :label="$options.i18n.formLabel" + label-for="password-prompt-confirmation" + :invalid-feedback="$options.i18n.errorMessage" + :state="isValid" + > + <gl-form-input + id="password-prompt-confirmation" + v-model="passwordCheck" + name="password-confirmation" + type="password" + data-testid="password-prompt-field" + /> + </gl-form-group> + </gl-form> + </gl-modal> +</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js index 02baa76f627..d8f15cfde91 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js @@ -2,51 +2,51 @@ import { s__ } from '~/locale'; export const PIPELINE_SOURCES = [ { - text: s__('Pipeline|Source|Push'), + text: s__('PipelineSource|Push'), value: 'push', }, { - text: s__('Pipeline|Source|Web'), + text: s__('PipelineSource|Web'), value: 'web', }, { - text: s__('Pipeline|Source|Trigger'), + text: s__('PipelineSource|Trigger'), value: 'trigger', }, { - text: s__('Pipeline|Source|Schedule'), + text: s__('PipelineSource|Schedule'), value: 'schedule', }, { - text: s__('Pipeline|Source|API'), + text: s__('PipelineSource|API'), value: 'api', }, { - text: s__('Pipeline|Source|External'), + text: s__('PipelineSource|External'), value: 'external', }, { - text: s__('Pipeline|Source|Pipeline'), + text: s__('PipelineSource|Pipeline'), value: 'pipeline', }, { - text: s__('Pipeline|Source|Chat'), + text: s__('PipelineSource|Chat'), value: 'chat', }, { - text: s__('Pipeline|Source|Web IDE'), + text: s__('PipelineSource|Web IDE'), value: 'webide', }, { - text: s__('Pipeline|Source|Merge Request'), + text: s__('PipelineSource|Merge Request'), value: 'merge_request_event', }, { - text: s__('Pipeline|Source|External Pull Request'), + text: s__('PipelineSource|External Pull Request'), value: 'external_pull_request_event', }, { - text: s__('Pipeline|Source|Parent Pipeline'), + text: s__('PipelineSource|Parent Pipeline'), value: 'parent_pipeline', }, ]; diff --git a/app/assets/javascripts/related_issues/components/related_issues_block.vue b/app/assets/javascripts/related_issues/components/related_issues_block.vue index c042f0eef5f..7d23c7033f3 100644 --- a/app/assets/javascripts/related_issues/components/related_issues_block.vue +++ b/app/assets/javascripts/related_issues/components/related_issues_block.vue @@ -123,7 +123,7 @@ export default { </script> <template> - <div id="related-issues" class="related-issues-block"> + <div id="related-issues" class="related-issues-block gl-mt-5"> <div class="card card-slim gl-overflow-hidden"> <div :class="{ 'panel-empty-heading border-bottom-0': !hasBody }" diff --git a/app/assets/javascripts/related_issues/components/related_issues_list.vue b/app/assets/javascripts/related_issues/components/related_issues_list.vue index 8f486fb1b07..a21e294a34a 100644 --- a/app/assets/javascripts/related_issues/components/related_issues_list.vue +++ b/app/assets/javascripts/related_issues/components/related_issues_list.vue @@ -97,11 +97,7 @@ export default { class="related-issues-token-body bordered-box bg-white" :class="{ 'sortable-container': canReorder }" > - <div - v-if="isFetching" - class="related-issues-loading-icon" - data-qa-selector="related_issues_loading_placeholder" - > + <div v-if="isFetching" class="gl-mb-2" data-qa-selector="related_issues_loading_placeholder"> <gl-loading-icon ref="loadingIcon" size="sm" diff --git a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue index db2197ec65e..4564a48fa2d 100644 --- a/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue +++ b/app/assets/javascripts/sidebar/components/time_tracking/spent_only_pane.vue @@ -1,30 +1,31 @@ <script> -import { sprintf, s__ } from '~/locale'; +import { GlSprintf } from '@gitlab/ui'; +import { s__ } from '~/locale'; + +const timeSpent = s__('TimeTracking|%{spentStart}Spent: %{spentEnd}'); export default { name: 'TimeTrackingSpentOnlyPane', + timeSpent, + components: { + GlSprintf, + }, props: { timeSpentHumanReadable: { type: String, required: true, }, }, - computed: { - timeSpent() { - return sprintf( - s__('TimeTracking|%{startTag}Spent: %{endTag}%{timeSpentHumanReadable}'), - { - startTag: '<span class="gl-font-weight-bold">', - endTag: '</span>', - timeSpentHumanReadable: this.timeSpentHumanReadable, - }, - false, - ); - }, - }, }; </script> <template> - <div data-testid="spentOnlyPane" v-html="timeSpent /* eslint-disable-line vue/no-v-html */"></div> + <div data-testid="spentOnlyPane"> + <gl-sprintf :message="$options.timeSpent"> + <template #spent="{ content }"> + <span class="gl-font-weight-bold">{{ content }}</span + >{{ timeSpentHumanReadable }} + </template> + </gl-sprintf> + </div> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue index ba97bb767e2..08f4837a5cf 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue @@ -1,7 +1,8 @@ <script> import { GlButton, GlLoadingIcon, GlIcon, GlLink, GlBadge, GlSafeHtmlDirective } from '@gitlab/ui'; import SmartVirtualList from '~/vue_shared/components/smart_virtual_list.vue'; -import StatusIcon from '../mr_widget_status_icon.vue'; +import { EXTENSION_ICON_CLASS } from '../../constants'; +import StatusIcon from './status_icon.vue'; export const LOADING_STATES = { collapsedLoading: 'collapsedLoading', @@ -45,14 +46,6 @@ export default { return true; }, statusIconName() { - if (this.isLoadingSummary) { - return 'loading'; - } - - if (this.loadingState === LOADING_STATES.collapsedError) { - return 'warning'; - } - return this.statusIcon(this.collapsedData); }, }, @@ -96,13 +89,18 @@ export default { }); }, }, + EXTENSION_ICON_CLASS, }; </script> <template> - <section class="media-section mr-widget-border-top"> + <section class="media-section mr-widget-border-top" data-testid="widget-extension"> <div class="media gl-p-5"> - <status-icon :status="statusIconName" class="align-self-center" /> + <status-icon + :name="$options.name" + :is-loading="isLoadingSummary" + :icon-name="statusIconName" + /> <div class="media-body d-flex flex-align-self-center align-items-center"> <div class="code-text"> <template v-if="isLoadingSummary"> @@ -114,13 +112,18 @@ export default { v-if="isCollapsible" size="small" class="float-right align-self-center" + data-testid="toggle-button" @click="toggleCollapsed" > {{ isCollapsed ? __('Expand') : __('Collapse') }} </gl-button> </div> </div> - <div v-if="!isCollapsed" class="mr-widget-grouped-section"> + <div + v-if="!isCollapsed" + class="mr-widget-grouped-section" + data-testid="widget-extension-collapsed-section" + > <div v-if="isLoadingExpanded" class="report-block-container"> <gl-loading-icon size="sm" inline /> {{ __('Loading...') }} </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js index 2fd1e17aaa6..46046d16fcf 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/container.js @@ -1,4 +1,4 @@ -import { extensions } from './index'; +import { registeredExtensions } from './index'; export default { props: { @@ -8,6 +8,8 @@ export default { }, }, render(h) { + const { extensions } = registeredExtensions; + if (extensions.length === 0) return null; return h('div', {}, [ diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js index 9796bb44939..bb40e22fe3f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/index.js @@ -1,12 +1,13 @@ +import Vue from 'vue'; import ExtensionBase from './base.vue'; // Holds all the currently registered extensions -export const extensions = []; +export const registeredExtensions = Vue.observable({ extensions: [] }); export const registerExtension = (extension) => { // Pushes into the extenions array a dynamically created Vue component // that gets exteneded from `base.vue` - extensions.push({ + registeredExtensions.extensions.push({ extends: ExtensionBase, name: extension.name, props: extension.props, diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue new file mode 100644 index 00000000000..ed6aa8be1d5 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/status_icon.vue @@ -0,0 +1,52 @@ +<script> +import { GlLoadingIcon, GlIcon } from '@gitlab/ui'; +import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; +import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants'; + +export default { + components: { + GlLoadingIcon, + GlIcon, + }, + props: { + name: { + type: String, + required: true, + }, + isLoading: { + type: Boolean, + required: true, + }, + iconName: { + type: String, + required: true, + }, + }, + computed: { + iconAriaLabel() { + const statusLabel = Object.keys(EXTENSION_ICONS).find( + (k) => EXTENSION_ICONS[k] === this.iconName, + ); + + return `${capitalizeFirstCharacter(statusLabel)} ${this.name}`; + }, + }, + EXTENSION_ICON_CLASS, +}; +</script> + +<template> + <div + :class="[$options.EXTENSION_ICON_CLASS[iconName], { 'mr-widget-extension-icon': !isLoading }]" + class="align-self-center gl-rounded-full gl-mr-3 gl-relative gl-p-2" + > + <gl-loading-icon v-if="isLoading" size="md" inline class="gl-display-block" /> + <gl-icon + v-else + :name="iconName" + :size="16" + :aria-label="iconAriaLabel" + class="gl-display-block" + /> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/constants.js b/app/assets/javascripts/vue_merge_request_widget/constants.js index 5edd4684529..c02783be385 100644 --- a/app/assets/javascripts/vue_merge_request_widget/constants.js +++ b/app/assets/javascripts/vue_merge_request_widget/constants.js @@ -91,4 +91,19 @@ export const stateToTransitionMap = { export const stateToComponentMap = { [states.MERGING]: classStateMap[stateKey.merging], }; + +export const EXTENSION_ICONS = { + failed: 'status-failed', + warning: 'status-alert', + success: 'status-success', + neutral: 'status-neutral', +}; + +export const EXTENSION_ICON_CLASS = { + [EXTENSION_ICONS.failed]: 'gl-text-red-500', + [EXTENSION_ICONS.warning]: 'gl-text-orange-500', + [EXTENSION_ICONS.success]: 'gl-text-green-500', + [EXTENSION_ICONS.neutral]: 'gl-text-gray-400', +}; + export { STATE_MACHINE }; diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js index 6c6f5e7fc73..5eda5c0778f 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js @@ -1,11 +1,12 @@ /* eslint-disable */ +import { EXTENSION_ICONS } from '../constants'; import issuesCollapsedQuery from './issues_collapsed.query.graphql'; import issuesQuery from './issues.query.graphql'; export default { // Give the extension a name // Make it easier to track in Vue dev tools - name: 'WidgetIssues', + name: 'Issues', // Add an array of props // These then get mapped to values stored in the MR Widget store props: ['targetProjectFullPath'], @@ -14,12 +15,12 @@ export default { // Small summary text to be displayed in the collapsed state // Receives the collapsed data as an argument summary(count) { - return `<strong>${count}</strong> open issue`; + return 'Summary text'; }, // Status icon to be used next to the summary text // Receives the collapsed data as an argument statusIcon(count) { - return count > 0 ? 'warning' : 'success'; + return EXTENSION_ICONS.warning; }, }, methods: { diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue index 7b88b36aa0f..ea507017caa 100644 --- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue +++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/markdown_viewer.vue @@ -97,7 +97,7 @@ export default { }); }) .catch(() => { - this.previewContent = __('An error occurred while fetching markdown preview'); + this.previewContent = __('An error occurred while fetching Markdown preview'); this.isLoading = false; }); } diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 77730ada9bb..86f04c78ebe 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -254,7 +254,7 @@ export default { .then(() => $(this.$refs['markdown-preview']).renderGFM()) .catch(() => createFlash({ - message: __('Error rendering markdown preview'), + message: __('Error rendering Markdown preview'), }), ); }, diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 071a5be073f..59520e629ab 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -1040,3 +1040,17 @@ $tabs-holder-z-index: 250; margin-bottom: 1px; } } + +.mr-widget-extension-icon::before { + @include gl-content-empty; + @include gl-absolute; + @include gl-left-0; + @include gl-top-0; + @include gl-opacity-3; + @include gl-border-solid; + @include gl-border-4; + @include gl-rounded-full; + + width: 24px; + height: 24px; +} |