diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-12 15:13:54 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-05-12 15:13:54 +0000 |
commit | 98638cd5e43611aac2193a5c2f80f72374040430 (patch) | |
tree | 6605f0f284efed1d05708b3799f093eb5e305a8f /app/assets | |
parent | 43d816ebc20da6ff959176248c70d8c4c7c9345a (diff) | |
download | gitlab-ce-98638cd5e43611aac2193a5c2f80f72374040430.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets')
30 files changed, 705 insertions, 236 deletions
diff --git a/app/assets/javascripts/code_review/signals.js b/app/assets/javascripts/code_review/signals.js new file mode 100644 index 00000000000..101b7996bb5 --- /dev/null +++ b/app/assets/javascripts/code_review/signals.js @@ -0,0 +1,51 @@ +import createApolloClient from '../lib/graphql'; + +import { getDerivedMergeRequestInformation } from '../diffs/utils/merge_request'; +import { EVT_MR_PREPARED } from '../diffs/constants'; + +import getMr from '../graphql_shared/queries/merge_request.query.graphql'; +import mrPreparation from '../graphql_shared/subscriptions/merge_request_prepared.subscription.graphql'; + +function required(name) { + throw new Error(`${name} is a required argument`); +} + +async function observeMergeRequestFinishingPreparation({ apollo, signaler }) { + const { namespace, project, id: iid } = getDerivedMergeRequestInformation({ + endpoint: document.location.pathname, + }); + const projectPath = `${namespace}/${project}`; + + if (projectPath && iid) { + const currentStatus = await apollo.query({ + query: getMr, + variables: { projectPath, iid }, + }); + const { id: gqlMrId, preparedAt } = currentStatus.data.project.mergeRequest; + let preparationObservable; + let preparationSubscriber; + + if (!preparedAt) { + preparationObservable = apollo.subscribe({ + query: mrPreparation, + variables: { + issuableId: gqlMrId, + }, + }); + + preparationSubscriber = preparationObservable.subscribe((preparationUpdate) => { + if (preparationUpdate.data.mergeRequestMergeStatusUpdated?.preparedAt) { + signaler.$emit(EVT_MR_PREPARED); + preparationSubscriber.unsubscribe(); + } + }); + } + } +} + +export async function start({ + signalBus = required('signalBus'), + apolloClient = createApolloClient(), +} = {}) { + await observeMergeRequestFinishingPreparation({ signaler: signalBus, apollo: apolloClient }); +} diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/formatting_bubble_menu.vue b/app/assets/javascripts/content_editor/components/bubble_menus/formatting_bubble_menu.vue deleted file mode 100644 index cef446c4cf8..00000000000 --- a/app/assets/javascripts/content_editor/components/bubble_menus/formatting_bubble_menu.vue +++ /dev/null @@ -1,130 +0,0 @@ -<script> -import { GlButtonGroup } from '@gitlab/ui'; -import { BUBBLE_MENU_TRACKING_ACTION } from '../../constants'; -import trackUIControl from '../../services/track_ui_control'; -import Paragraph from '../../extensions/paragraph'; -import Heading from '../../extensions/heading'; -import Audio from '../../extensions/audio'; -import Video from '../../extensions/video'; -import Image from '../../extensions/image'; -import DrawioDiagram from '../../extensions/drawio_diagram'; -import ToolbarButton from '../toolbar_button.vue'; -import BubbleMenu from './bubble_menu.vue'; - -export default { - components: { - BubbleMenu, - GlButtonGroup, - ToolbarButton, - }, - inject: ['tiptapEditor'], - methods: { - trackToolbarControlExecution({ contentType, value }) { - trackUIControl({ action: BUBBLE_MENU_TRACKING_ACTION, property: contentType, value }); - }, - - shouldShow: ({ editor, from, to }) => { - if (from === to) return false; - - const includes = [Paragraph.name, Heading.name]; - const excludes = [Image.name, Audio.name, Video.name, DrawioDiagram.name]; - - return ( - includes.some((type) => editor.isActive(type)) && - !excludes.some((type) => editor.isActive(type)) - ); - }, - }, -}; -</script> -<template> - <bubble-menu - data-testid="formatting-bubble-menu" - class="gl-shadow gl-rounded-base gl-bg-white" - :should-show="shouldShow" - :plugin-key="'formatting'" - > - <gl-button-group> - <toolbar-button - data-testid="bold" - content-type="bold" - icon-name="bold" - editor-command="toggleBold" - category="tertiary" - size="medium" - :label="__('Bold text')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="italic" - content-type="italic" - icon-name="italic" - editor-command="toggleItalic" - category="tertiary" - size="medium" - :label="__('Italic text')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="strike" - content-type="strike" - icon-name="strikethrough" - editor-command="toggleStrike" - category="tertiary" - size="medium" - :label="__('Strikethrough')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="code" - content-type="code" - icon-name="code" - editor-command="toggleCode" - category="tertiary" - size="medium" - :label="__('Code')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="superscript" - content-type="superscript" - icon-name="superscript" - editor-command="toggleSuperscript" - category="tertiary" - size="medium" - :label="__('Superscript')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="subscript" - content-type="subscript" - icon-name="subscript" - editor-command="toggleSubscript" - category="tertiary" - size="medium" - :label="__('Subscript')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="highlight" - content-type="highlight" - icon-name="highlight" - editor-command="toggleHighlight" - category="tertiary" - size="medium" - :label="__('Highlight')" - @execute="trackToolbarControlExecution" - /> - <toolbar-button - data-testid="link" - content-type="link" - icon-name="link" - editor-command="editLink" - category="tertiary" - size="medium" - :label="__('Insert link')" - @execute="trackToolbarControlExecution" - /> - </gl-button-group> - </bubble-menu> -</template> diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index 7fee798c65a..4c5bbca4110 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -8,7 +8,6 @@ import { ALERT_EVENT, TIPTAP_AUTOFOCUS_OPTIONS } from '../constants'; import ContentEditorAlert from './content_editor_alert.vue'; import ContentEditorProvider from './content_editor_provider.vue'; import EditorStateObserver from './editor_state_observer.vue'; -import FormattingBubbleMenu from './bubble_menus/formatting_bubble_menu.vue'; import CodeBlockBubbleMenu from './bubble_menus/code_block_bubble_menu.vue'; import LinkBubbleMenu from './bubble_menus/link_bubble_menu.vue'; import MediaBubbleMenu from './bubble_menus/media_bubble_menu.vue'; @@ -24,7 +23,6 @@ export default { ContentEditorProvider, TiptapEditorContent, FormattingToolbar, - FormattingBubbleMenu, CodeBlockBubbleMenu, LinkBubbleMenu, MediaBubbleMenu, @@ -225,7 +223,6 @@ export default { > <formatting-toolbar ref="toolbar" @enableMarkdownEditor="$emit('enableMarkdownEditor')" /> <div class="gl-relative"> - <formatting-bubble-menu /> <code-block-bubble-menu /> <link-bubble-menu /> <media-bubble-menu /> diff --git a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue index 1ef38df0f78..fac259cf6a1 100644 --- a/app/assets/javascripts/content_editor/components/formatting_toolbar.vue +++ b/app/assets/javascripts/content_editor/components/formatting_toolbar.vue @@ -54,6 +54,14 @@ export default { @execute="trackToolbarControlExecution" /> <toolbar-button + data-testid="strike" + content-type="strike" + icon-name="strikethrough" + editor-command="toggleStrike" + :label="__('Strikethrough')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button data-testid="blockquote" content-type="blockquote" icon-name="quote" diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index bfabac3123b..02307150e2f 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -41,6 +41,7 @@ import { TRACKING_WHITESPACE_HIDE, TRACKING_SINGLE_FILE_MODE, TRACKING_MULTIPLE_FILES_MODE, + EVT_MR_PREPARED, } from '../constants'; import diffsEventHub from '../event_hub'; @@ -470,8 +471,10 @@ export default { diffsEventHub.$on('diffFilesModified', this.setDiscussions); notesEventHub.$on('fetchedNotesData', this.rereadNoteHash); } + diffsEventHub.$on(EVT_MR_PREPARED, this.fetchData); }, unsubscribeFromEvents() { + diffsEventHub.$off(EVT_MR_PREPARED, this.fetchData); if (this.glFeatures.singleFileFileByFile) { notesEventHub.$off('fetchedNotesData', this.rereadNoteHash); diffsEventHub.$off('diffFilesModified', this.setDiscussions); diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js index a459def6b4b..063e36fa7fb 100644 --- a/app/assets/javascripts/diffs/constants.js +++ b/app/assets/javascripts/diffs/constants.js @@ -79,6 +79,7 @@ export const RENAMED_DIFF_TRANSITIONS = { }; // MR Diffs known events +export const EVT_MR_PREPARED = 'mr:asyncPreparationFinished'; export const EVT_EXPAND_ALL_FILES = 'mr:diffs:expandAllFiles'; export const EVT_PERF_MARK_FILE_TREE_START = 'mr:diffs:perf:fileTreeStart'; export const EVT_PERF_MARK_FILE_TREE_END = 'mr:diffs:perf:fileTreeEnd'; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 2a7b4da684b..0668551902a 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -49,6 +49,7 @@ import { TRACKING_CLICK_SINGLE_FILE_SETTING, TRACKING_SINGLE_FILE_MODE, TRACKING_MULTIPLE_FILES_MODE, + EVT_MR_PREPARED, } from '../constants'; import { DISCUSSION_SINGLE_DIFF_FAILED, LOAD_SINGLE_DIFF_FAILED } from '../i18n'; import eventHub from '../event_hub'; @@ -287,10 +288,14 @@ export const fetchDiffFilesMeta = ({ commit, state }) => { }) .catch((error) => { if (error.response.status === HTTP_STATUS_NOT_FOUND) { - createAlert({ - message: __('Building your merge request. Wait a few moments, then refresh this page.'), + const alert = createAlert({ + message: __( + 'Building your merge request… This page will update when the build is complete.', + ), variant: VARIANT_WARNING, }); + + eventHub.$once(EVT_MR_PREPARED, () => alert.dismiss()); } else { throw error; } diff --git a/app/assets/javascripts/environments/components/deploy_freeze_alert.vue b/app/assets/javascripts/environments/components/deploy_freeze_alert.vue new file mode 100644 index 00000000000..aaa7e71758c --- /dev/null +++ b/app/assets/javascripts/environments/components/deploy_freeze_alert.vue @@ -0,0 +1,79 @@ +<script> +import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; +import { sortBy } from 'lodash'; +import { formatDate } from '~/lib/utils/datetime/date_format_utility'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { s__ } from '~/locale'; +import deployFreezesQuery from '../graphql/queries/deploy_freezes.query.graphql'; + +export default { + components: { + GlAlert, + GlLink, + GlSprintf, + }, + inject: ['projectFullPath'], + props: { + name: { + type: String, + required: true, + }, + }, + data() { + return { deployFreezes: [] }; + }, + + apollo: { + deployFreezes: { + query: deployFreezesQuery, + update(data) { + const freezes = data?.project?.environment?.deployFreezes; + return sortBy(freezes, [(freeze) => freeze.startTime]); + }, + variables() { + return { + projectFullPath: this.projectFullPath, + environmentName: this.name, + }; + }, + }, + }, + computed: { + shouldShowDeployFreezeAlert() { + return this.deployFreezes.length > 0; + }, + nextDeployFreeze() { + return this.deployFreezes[0]; + }, + deployFreezeStartTime() { + return formatDate(this.nextDeployFreeze.startTime); + }, + deployFreezeEndTime() { + return formatDate(this.nextDeployFreeze.endTime); + }, + }, + i18n: { + deployFreezeAlert: s__( + 'Environments|A freeze period is in effect from %{startTime} to %{endTime}. Deployments might fail during this time. For more information, see the %{docsLinkStart}deploy freeze documentation%{docsLinkEnd}.', + ), + }, + deployFreezeDocsPath: helpPagePath('user/project/releases/index', { + anchor: 'prevent-unintentional-releases-by-setting-a-deploy-freeze', + }), +}; +</script> +<template> + <gl-alert v-if="shouldShowDeployFreezeAlert" :dismissible="false" class="gl-mt-4"> + <gl-sprintf :message="$options.i18n.deployFreezeAlert"> + <template #startTime + ><span class="gl-font-weight-bold">{{ deployFreezeStartTime }}</span></template + > + <template #endTime + ><span class="gl-font-weight-bold">{{ deployFreezeEndTime }}</span></template + > + <template #docsLink="{ content }" + ><gl-link :href="$options.deployFreezeDocsPath">{{ content }}</gl-link></template + > + </gl-sprintf> + </gl-alert> +</template> diff --git a/app/assets/javascripts/environments/components/environments_detail_header.vue b/app/assets/javascripts/environments/components/environments_detail_header.vue index 1e555347011..0507abf3eaf 100644 --- a/app/assets/javascripts/environments/components/environments_detail_header.vue +++ b/app/assets/javascripts/environments/components/environments_detail_header.vue @@ -7,6 +7,7 @@ import timeagoMixin from '~/vue_shared/mixins/timeago'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import DeleteEnvironmentModal from './delete_environment_modal.vue'; import StopEnvironmentModal from './stop_environment_modal.vue'; +import DeployFreezeAlert from './deploy_freeze_alert.vue'; export default { name: 'EnvironmentsDetailHeader', @@ -15,6 +16,7 @@ export default { GlButton, GlSprintf, TimeAgo, + DeployFreezeAlert, DeleteEnvironmentModal, StopEnvironmentModal, }, @@ -96,81 +98,88 @@ export default { }; </script> <template> - <header class="top-area gl-justify-content-between"> - <div class="gl-display-flex gl-flex-grow-1 gl-align-items-center"> - <h1 class="page-title gl-font-size-h-display"> - {{ environment.name }} - </h1> - <p v-if="shouldShowCancelAutoStopButton" class="gl-mb-0 gl-ml-3" data-testid="auto-stops-at"> - <gl-sprintf :message="$options.i18n.autoStopAtText"> - <template #autoStopAt> - <time-ago :time="environment.autoStopAt" /> - </template> - </gl-sprintf> - </p> - </div> - <div class="nav-controls gl-my-1"> - <form method="POST" :action="cancelAutoStopPath" data-testid="cancel-auto-stop-form"> - <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> - <gl-button + <div> + <deploy-freeze-alert :name="environment.name" /> + <header class="top-area gl-justify-content-between"> + <div class="gl-display-flex gl-flex-grow-1 gl-align-items-center"> + <h1 class="page-title gl-font-size-h-display"> + {{ environment.name }} + </h1> + <p v-if="shouldShowCancelAutoStopButton" - v-gl-tooltip.hover - data-testid="cancel-auto-stop-button" - :title="$options.i18n.cancelAutoStopButtonTitle" - type="submit" - icon="thumbtack" + class="gl-mb-0 gl-ml-3" + data-testid="auto-stops-at" + > + <gl-sprintf :message="$options.i18n.autoStopAtText"> + <template #autoStopAt> + <time-ago :time="environment.autoStopAt" /> + </template> + </gl-sprintf> + </p> + </div> + <div class="nav-controls gl-my-1"> + <form method="POST" :action="cancelAutoStopPath" data-testid="cancel-auto-stop-form"> + <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> + <gl-button + v-if="shouldShowCancelAutoStopButton" + v-gl-tooltip.hover + data-testid="cancel-auto-stop-button" + :title="$options.i18n.cancelAutoStopButtonTitle" + type="submit" + icon="thumbtack" + /> + </form> + <gl-button + v-if="shouldShowTerminalButton" + data-testid="terminal-button" + :href="terminalPath" + icon="terminal" /> - </form> - <gl-button - v-if="shouldShowTerminalButton" - data-testid="terminal-button" - :href="terminalPath" - icon="terminal" - /> - <gl-button - v-if="shouldShowExternalUrlButton" - v-gl-tooltip.hover - data-testid="external-url-button" - :title="$options.i18n.externalButtonTitle" - :href="environment.externalUrl" - is-unsafe-link - icon="external-link" - target="_blank" - >{{ $options.i18n.externalButtonText }}</gl-button - > - <gl-button - v-if="shouldShowMetricsButton" - v-gl-tooltip.hover - data-testid="metrics-button" - :href="metricsPath" - :title="$options.i18n.metricsButtonTitle" - icon="chart" - class="gl-mr-2" - > - {{ $options.i18n.metricsButtonText }} - </gl-button> - <gl-button v-if="canUpdateEnvironment" data-testid="edit-button" :href="updatePath"> - {{ $options.i18n.editButtonText }} - </gl-button> - <gl-button - v-if="shouldShowStopButton" - v-gl-modal-directive="'stop-environment-modal'" - data-testid="stop-button" - icon="stop" - variant="danger" - > - {{ $options.i18n.stopButtonText }} - </gl-button> - <gl-button - v-if="canDestroyEnvironment" - v-gl-modal-directive="'delete-environment-modal'" - data-testid="destroy-button" - variant="danger" - > - {{ $options.i18n.deleteButtonText }} - </gl-button> - </div> - <delete-environment-modal v-if="canDestroyEnvironment" :environment="environment" /> - <stop-environment-modal v-if="shouldShowStopButton" :environment="environment" /> - </header> + <gl-button + v-if="shouldShowExternalUrlButton" + v-gl-tooltip.hover + data-testid="external-url-button" + :title="$options.i18n.externalButtonTitle" + :href="environment.externalUrl" + is-unsafe-link + icon="external-link" + target="_blank" + >{{ $options.i18n.externalButtonText }}</gl-button + > + <gl-button + v-if="shouldShowMetricsButton" + v-gl-tooltip.hover + data-testid="metrics-button" + :href="metricsPath" + :title="$options.i18n.metricsButtonTitle" + icon="chart" + class="gl-mr-2" + > + {{ $options.i18n.metricsButtonText }} + </gl-button> + <gl-button v-if="canUpdateEnvironment" data-testid="edit-button" :href="updatePath"> + {{ $options.i18n.editButtonText }} + </gl-button> + <gl-button + v-if="shouldShowStopButton" + v-gl-modal-directive="'stop-environment-modal'" + data-testid="stop-button" + icon="stop" + variant="danger" + > + {{ $options.i18n.stopButtonText }} + </gl-button> + <gl-button + v-if="canDestroyEnvironment" + v-gl-modal-directive="'delete-environment-modal'" + data-testid="destroy-button" + variant="danger" + > + {{ $options.i18n.deleteButtonText }} + </gl-button> + </div> + <delete-environment-modal v-if="canDestroyEnvironment" :environment="environment" /> + <stop-environment-modal v-if="shouldShowStopButton" :environment="environment" /> + </header> + </div> </template> diff --git a/app/assets/javascripts/environments/graphql/queries/deploy_freezes.query.graphql b/app/assets/javascripts/environments/graphql/queries/deploy_freezes.query.graphql new file mode 100644 index 00000000000..7d701b95bbf --- /dev/null +++ b/app/assets/javascripts/environments/graphql/queries/deploy_freezes.query.graphql @@ -0,0 +1,12 @@ +query getEnvironmentFreezes($projectFullPath: ID!, $environmentName: String) { + project(fullPath: $projectFullPath) { + id + environment(name: $environmentName) { + id + deployFreezes { + startTime + endTime + } + } + } +} diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js index cc13a237aca..f73cb7fe1bc 100644 --- a/app/assets/javascripts/environments/mount_show.js +++ b/app/assets/javascripts/environments/mount_show.js @@ -3,9 +3,13 @@ import VueApollo from 'vue-apollo'; import VueRouter from 'vue-router'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import EnvironmentsDetailHeader from './components/environments_detail_header.vue'; -import { apolloProvider } from './graphql/client'; +import { apolloProvider as createApolloProvider } from './graphql/client'; import environmentsMixin from './mixins/environments_mixin'; +Vue.use(VueApollo); + +const apolloProvider = createApolloProvider(); + export const initHeader = () => { const el = document.getElementById('environments-detail-view-header'); const container = document.getElementById('environments-detail-view'); @@ -13,7 +17,11 @@ export const initHeader = () => { return new Vue({ el, + apolloProvider, mixins: [environmentsMixin], + provide: { + projectFullPath: dataset.projectFullPath, + }, data() { const environment = { name: dataset.name, @@ -60,7 +68,6 @@ export const initPage = async () => { const dataElement = document.getElementById('environments-detail-view'); const dataSet = convertObjectPropsToCamelCase(JSON.parse(dataElement.dataset.details)); - Vue.use(VueApollo); Vue.use(VueRouter); const el = document.getElementById('environment_details_page'); @@ -90,7 +97,7 @@ export const initPage = async () => { return new Vue({ el, - apolloProvider: apolloProvider(), + apolloProvider, router, provide: { projectPath: dataSet.projectFullPath, diff --git a/app/assets/javascripts/graphql_shared/queries/merge_request.query.graphql b/app/assets/javascripts/graphql_shared/queries/merge_request.query.graphql new file mode 100644 index 00000000000..a6ef5935162 --- /dev/null +++ b/app/assets/javascripts/graphql_shared/queries/merge_request.query.graphql @@ -0,0 +1,9 @@ +query mergeRequestId($projectPath: ID!, $iid: String!) { + project(fullPath: $projectPath) { + id + mergeRequest(iid: $iid) { + id + preparedAt + } + } +} diff --git a/app/assets/javascripts/graphql_shared/subscriptions/merge_request_prepared.subscription.graphql b/app/assets/javascripts/graphql_shared/subscriptions/merge_request_prepared.subscription.graphql new file mode 100644 index 00000000000..ba658f56ebd --- /dev/null +++ b/app/assets/javascripts/graphql_shared/subscriptions/merge_request_prepared.subscription.graphql @@ -0,0 +1,8 @@ +subscription mergeRequestPrepared($issuableId: IssuableID!) { + mergeRequestMergeStatusUpdated(issuableId: $issuableId) { + ... on MergeRequest { + id + preparedAt + } + } +} diff --git a/app/assets/javascripts/pages/projects/merge_requests/page.js b/app/assets/javascripts/pages/projects/merge_requests/page.js index fbd45f4bd7d..552e75da9b8 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/page.js +++ b/app/assets/javascripts/pages/projects/merge_requests/page.js @@ -3,6 +3,8 @@ import VueApollo from 'vue-apollo'; import initMrNotes from 'ee_else_ce/mr_notes'; import StickyHeader from '~/merge_requests/components/sticky_header.vue'; import { initIssuableHeaderWarnings } from '~/issuable'; +import { start as startCodeReviewMessaging } from '~/code_review/signals'; +import diffsEventHub from '~/diffs/event_hub'; import store from '~/mr_notes/stores'; import initSidebarBundle from '~/sidebar/sidebar_bundle'; import { apolloProvider } from '~/graphql_shared/issuable_client'; @@ -15,6 +17,7 @@ Vue.use(VueApollo); export function initMrPage() { initMrNotes(); initShow(); + startCodeReviewMessaging({ signalBus: diffsEventHub }); } requestIdleCallback(() => { diff --git a/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql index 13c9f0ff8ee..5bdafa15f72 100644 --- a/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql +++ b/app/assets/javascripts/pipelines/graphql/queries/get_failed_jobs.query.graphql @@ -3,7 +3,7 @@ query getFailedJobs($fullPath: ID!, $pipelineIid: ID!) { id pipeline(iid: $pipelineIid) { id - jobs(statuses: FAILED) { + jobs(statuses: FAILED, retried: false) { nodes { status detailedStatus { diff --git a/app/assets/javascripts/projects/commit_box/info/init_details_button.js b/app/assets/javascripts/projects/commit_box/info/init_details_button.js index 667d6bd0250..520b20fcb86 100644 --- a/app/assets/javascripts/projects/commit_box/info/init_details_button.js +++ b/app/assets/javascripts/projects/commit_box/info/init_details_button.js @@ -6,7 +6,7 @@ export const initDetailsButton = () => { } expandButton.addEventListener('click', (event) => { - const btn = event.target; + const btn = event.currentTarget; const contentEl = btn.parentElement.querySelector('.js-details-content'); if (contentEl) { diff --git a/app/assets/javascripts/work_items/components/work_item_award_emoji.vue b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue new file mode 100644 index 00000000000..91f87be1233 --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_award_emoji.vue @@ -0,0 +1,144 @@ +<script> +import * as Sentry from '@sentry/browser'; +import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils'; +import AwardsList from '~/vue_shared/components/awards_list.vue'; +import { isLoggedIn } from '~/lib/utils/common_utils'; +import { TYPENAME_USER } from '~/graphql_shared/constants'; +import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; +import { + EMOJI_ACTION_REMOVE, + EMOJI_ACTION_ADD, + WIDGET_TYPE_AWARD_EMOJI, + EMOJI_THUMBSDOWN, + EMOJI_THUMBSUP, +} from '../constants'; + +export default { + defaultAwards: [EMOJI_THUMBSUP, EMOJI_THUMBSDOWN], + isLoggedIn: isLoggedIn(), + components: { + AwardsList, + }, + props: { + workItem: { + type: Object, + required: true, + }, + awardEmoji: { + type: Object, + required: true, + }, + }, + computed: { + currentUserId() { + return window.gon.current_user_id; + }, + /** + * Parse and convert award emoji list to a format that AwardsList can understand + */ + awards() { + return this.awardEmoji.nodes.map((emoji, index) => ({ + id: index + 1, + name: emoji.name, + user: { + id: getIdFromGraphQLId(emoji.user.id), + }, + })); + }, + }, + methods: { + handleAward(name) { + // Decide action based on emoji is already present + const action = + this.awards.findIndex((emoji) => emoji.name === name) > -1 + ? EMOJI_ACTION_REMOVE + : EMOJI_ACTION_ADD; + const inputVariables = { + id: this.workItem.id, + awardEmojiWidget: { + action, + name, + }, + }; + + this.$apollo + .mutate({ + mutation: updateWorkItemMutation, + variables: { + input: inputVariables, + }, + optimisticResponse: this.getOptimisticResponse({ name, action }), + }) + .then( + ({ + data: { + workItemUpdate: { errors }, + }, + }) => { + if (errors?.length) { + throw new Error(errors[0]); + } + }, + ) + .catch((error) => { + this.$emit('error', error.message); + Sentry.captureException(error); + }); + }, + /** + * Prepare workItemUpdate for optimistic response + */ + getOptimisticResponse({ name, action }) { + let awardEmojiNodes = [ + ...this.awardEmoji.nodes, + { + name, + __typename: 'AwardEmoji', + user: { + id: convertToGraphQLId(TYPENAME_USER, this.currentUserId), + __typename: 'UserCore', + }, + }, + ]; + // Exclude the award emoji node in case of remove action + if (action === EMOJI_ACTION_REMOVE) { + awardEmojiNodes = [...this.awardEmoji.nodes.filter((emoji) => emoji.name !== name)]; + } + return { + workItemUpdate: { + errors: [], + workItem: { + ...this.workItem, + widgets: [ + { + type: WIDGET_TYPE_AWARD_EMOJI, + awardEmoji: { + nodes: awardEmojiNodes, + __typename: 'AwardEmojiConnection', + }, + __typename: 'WorkItemWidgetAwardEmoji', + }, + ], + __typename: 'WorkItem', + }, + __typename: 'WorkItemUpdatePayload', + }, + }; + }, + }, +}; +</script> + +<template> + <div class="gl-mt-3"> + <awards-list + data-testid="work-item-award-list" + :awards="awards" + :can-award-emoji="$options.isLoggedIn" + :current-user-id="currentUserId" + :default-awards="$options.defaultAwards" + selected-class="selected" + @award="handleAward" + /> + </div> +</template> diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue index e3ac49b90d5..0f1af44e8a1 100644 --- a/app/assets/javascripts/work_items/components/work_item_detail.vue +++ b/app/assets/javascripts/work_items/components/work_item_detail.vue @@ -18,6 +18,7 @@ import { getParameterByName, updateHistory, setUrlParams } from '~/lib/utils/url import { isPositiveInteger } from '~/lib/utils/number_utils'; import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils'; +import { isLoggedIn } from '~/lib/utils/common_utils'; import { TYPENAME_WORK_ITEM } from '~/graphql_shared/constants'; import WorkItemTypeIcon from '~/work_items/components/work_item_type_icon.vue'; import AbuseCategorySelector from '~/abuse_reports/components/abuse_category_selector.vue'; @@ -27,7 +28,9 @@ import { WIDGET_TYPE_ASSIGNEES, WIDGET_TYPE_LABELS, WIDGET_TYPE_NOTIFICATIONS, + WIDGET_TYPE_CURRENT_USER_TODOS, WIDGET_TYPE_DESCRIPTION, + WIDGET_TYPE_AWARD_EMOJI, WIDGET_TYPE_START_AND_DUE_DATE, WIDGET_TYPE_WEIGHT, WIDGET_TYPE_PROGRESS, @@ -51,10 +54,12 @@ import { findHierarchyWidgetChildren } from '../utils'; import WorkItemTree from './work_item_links/work_item_tree.vue'; import WorkItemActions from './work_item_actions.vue'; +import WorkItemTodos from './work_item_todos.vue'; import WorkItemState from './work_item_state.vue'; import WorkItemTitle from './work_item_title.vue'; import WorkItemCreatedUpdated from './work_item_created_updated.vue'; import WorkItemDescription from './work_item_description.vue'; +import WorkItemAwardEmoji from './work_item_award_emoji.vue'; import WorkItemDueDate from './work_item_due_date.vue'; import WorkItemAssignees from './work_item_assignees.vue'; import WorkItemLabels from './work_item_labels.vue'; @@ -67,6 +72,7 @@ export default { directives: { GlTooltip: GlTooltipDirective, }, + isLoggedIn: isLoggedIn(), components: { GlAlert, GlBadge, @@ -77,8 +83,10 @@ export default { GlEmptyState, WorkItemAssignees, WorkItemActions, + WorkItemTodos, WorkItemCreatedUpdated, WorkItemDescription, + WorkItemAwardEmoji, WorkItemDueDate, WorkItemLabels, WorkItemTitle, @@ -287,6 +295,15 @@ export default { workItemNotificationsSubscribed() { return Boolean(this.isWidgetPresent(WIDGET_TYPE_NOTIFICATIONS)?.subscribed); }, + workItemCurrentUserTodos() { + return this.isWidgetPresent(WIDGET_TYPE_CURRENT_USER_TODOS); + }, + showWorkItemCurrentUserTodos() { + return this.$options.isLoggedIn && this.workItemCurrentUserTodos; + }, + currentUserTodos() { + return this.workItemCurrentUserTodos?.currentUserTodos?.edges; + }, workItemAssignees() { return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES); }, @@ -302,6 +319,9 @@ export default { workItemProgress() { return this.isWidgetPresent(WIDGET_TYPE_PROGRESS); }, + workItemAwardEmoji() { + return this.isWidgetPresent(WIDGET_TYPE_AWARD_EMOJI); + }, workItemHierarchy() { return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY); }, @@ -566,6 +586,12 @@ export default { class="gl-mr-3 gl-cursor-help" >{{ __('Confidential') }}</gl-badge > + <work-item-todos + v-if="showWorkItemCurrentUserTodos" + :work-item="workItem" + :current-user-todos="currentUserTodos" + @error="updateError = $event" + /> <work-item-actions v-if="canUpdate || canDelete" :work-item-id="workItem.id" @@ -685,6 +711,12 @@ export default { class="gl-pt-5" @error="updateError = $event" /> + <work-item-award-emoji + v-if="workItemAwardEmoji" + :work-item="workItem" + :award-emoji="workItemAwardEmoji.awardEmoji" + @error="updateError = $event" + /> <work-item-tree v-if="workItemType === $options.WORK_ITEM_TYPE_VALUE_OBJECTIVE" :work-item-type="workItemType" diff --git a/app/assets/javascripts/work_items/components/work_item_todos.vue b/app/assets/javascripts/work_items/components/work_item_todos.vue new file mode 100644 index 00000000000..4e787720a42 --- /dev/null +++ b/app/assets/javascripts/work_items/components/work_item_todos.vue @@ -0,0 +1,116 @@ +<script> +import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui'; +import { s__ } from '~/locale'; +import { updateGlobalTodoCount } from '~/sidebar/utils'; +import { getWorkItemTodoOptimisticResponse } from '../utils'; +import { ADD, MARK_AS_DONE, TODO_ADD_ICON, TODO_DONE_ICON } from '../constants'; +import updateWorkItemMutation from '../graphql/update_work_item.mutation.graphql'; + +export default { + i18n: { + addATodo: s__('WorkItem|Add a to do'), + markAsDone: s__('WorkItem|Mark as done'), + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + components: { + GlIcon, + GlButton, + }, + props: { + workItem: { + type: Object, + required: true, + }, + currentUserTodos: { + type: Array, + required: false, + default: () => [], + }, + }, + data() { + return { + isLoading: false, + buttonLabel: + this.currentUserTodos.length > 0 + ? this.$options.i18n.markAsDone + : this.$options.i18n.addATodo, + }; + }, + computed: { + pendingTodo() { + return this.currentUserTodos.length > 0; + }, + buttonIcon() { + return this.pendingTodo ? TODO_DONE_ICON : TODO_ADD_ICON; + }, + }, + methods: { + onToggle() { + this.isLoading = true; + this.buttonLabel = ''; + const action = this.pendingTodo ? MARK_AS_DONE : ADD; + const inputVariables = { + id: this.workItem.id, + currentUserTodosWidget: { + action, + }, + }; + this.$apollo + .mutate({ + mutation: updateWorkItemMutation, + variables: { + input: inputVariables, + }, + optimisticResponse: getWorkItemTodoOptimisticResponse({ + workItem: this.workItem, + pendingTodo: this.pendingTodo, + }), + }) + .then( + ({ + data: { + workItemUpdate: { errors }, + }, + }) => { + if (errors?.length) { + throw new Error(errors[0]); + } + if (this.pendingTodo) { + updateGlobalTodoCount(1); + this.buttonLabel = this.$options.i18n.markAsDone; + } else { + updateGlobalTodoCount(-1); + this.buttonLabel = this.$options.i18n.addATodo; + } + }, + ) + .catch((error) => { + this.$emit('error', error.message); + }) + .finally(() => { + this.isLoading = false; + }); + }, + }, +}; +</script> + +<template> + <gl-button + v-gl-tooltip.hover + data-testid="work-item-todos-action" + :loading="isLoading" + :title="buttonLabel" + category="tertiary" + :aria-label="buttonLabel" + @click="onToggle" + > + <gl-icon + data-testid="work-item-todos-icon" + :class="{ 'gl-fill-blue-500': pendingTodo }" + :name="buttonIcon" + /> + </gl-button> +</template> diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 6523734a382..6710c762c2e 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -14,7 +14,9 @@ export const TASK_TYPE_NAME = 'Task'; export const WIDGET_TYPE_ASSIGNEES = 'ASSIGNEES'; export const WIDGET_TYPE_DESCRIPTION = 'DESCRIPTION'; +export const WIDGET_TYPE_AWARD_EMOJI = 'AWARD_EMOJI'; export const WIDGET_TYPE_NOTIFICATIONS = 'NOTIFICATIONS'; +export const WIDGET_TYPE_CURRENT_USER_TODOS = 'CURRENT_USER_TODOS'; export const WIDGET_TYPE_LABELS = 'LABELS'; export const WIDGET_TYPE_START_AND_DUE_DATE = 'START_AND_DUE_DATE'; export const WIDGET_TYPE_WEIGHT = 'WEIGHT'; @@ -215,3 +217,19 @@ export const TEST_ID_NOTIFICATIONS_TOGGLE_ACTION = 'notifications-toggle-action' export const TEST_ID_NOTIFICATIONS_TOGGLE_FORM = 'notifications-toggle-form'; export const TEST_ID_DELETE_ACTION = 'delete-action'; export const TEST_ID_PROMOTE_ACTION = 'promote-action'; + +export const ADD = 'ADD'; +export const MARK_AS_DONE = 'MARK_AS_DONE'; +export const TODO_ADD_ICON = 'todo-add'; +export const TODO_DONE_ICON = 'todo-done'; +export const TODO_TYPENAME = 'Todo'; +export const TODO_EDGE_TYPENAME = 'TodoEdge'; +export const TODO_CONNECTION_TYPENAME = 'TodoConnection'; +export const CURRENT_USER_TODOS_TYPENAME = 'WorkItemWidgetCurrentUserTodos'; +export const WORK_ITEM_TYPENAME = 'WorkItem'; +export const WORK_ITEM_UPDATE_PAYLOAD_TYPENAME = 'WorkItemUpdatePayload'; + +export const EMOJI_ACTION_ADD = 'ADD'; +export const EMOJI_ACTION_REMOVE = 'REMOVE'; +export const EMOJI_THUMBSUP = 'thumbsup'; +export const EMOJI_THUMBSDOWN = 'thumbsdown'; diff --git a/app/assets/javascripts/work_items/graphql/award_emoji.fragment.graphql b/app/assets/javascripts/work_items/graphql/award_emoji.fragment.graphql new file mode 100644 index 00000000000..85b88990cd6 --- /dev/null +++ b/app/assets/javascripts/work_items/graphql/award_emoji.fragment.graphql @@ -0,0 +1,6 @@ +fragment AwardEmojiFragment on AwardEmoji { + name + user { + id + } +} diff --git a/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql index 44fda3ee894..42c057fb8fe 100644 --- a/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql @@ -1,6 +1,7 @@ #import "~/graphql_shared/fragments/label.fragment.graphql" #import "~/graphql_shared/fragments/user.fragment.graphql" #import "~/work_items/graphql/milestone.fragment.graphql" +#import "~/work_items/graphql/award_emoji.fragment.graphql" fragment WorkItemMetadataWidgets on WorkItemWidget { ... on WorkItemWidgetDescription { @@ -36,9 +37,28 @@ fragment WorkItemMetadataWidgets on WorkItemWidget { } } } - ... on WorkItemWidgetNotifications { type subscribed } + + ... on WorkItemWidgetCurrentUserTodos { + type + currentUserTodos(state: pending) { + edges { + node { + id + state + } + } + } + } + ... on WorkItemWidgetAwardEmoji { + type + awardEmoji { + nodes { + ...AwardEmojiFragment + } + } + } } diff --git a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql index 8039ef53f98..bf8dc9ce9b0 100644 --- a/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item_widgets.fragment.graphql @@ -1,6 +1,7 @@ #import "~/graphql_shared/fragments/label.fragment.graphql" #import "~/graphql_shared/fragments/user.fragment.graphql" #import "~/work_items/graphql/milestone.fragment.graphql" +#import "~/work_items/graphql/award_emoji.fragment.graphql" #import "ee_else_ce/work_items/graphql/work_item_metadata_widgets.fragment.graphql" fragment WorkItemWidgets on WorkItemWidget { @@ -89,4 +90,23 @@ fragment WorkItemWidgets on WorkItemWidget { type subscribed } + ... on WorkItemWidgetCurrentUserTodos { + type + currentUserTodos(state: pending) { + edges { + node { + id + state + } + } + } + } + ... on WorkItemWidgetAwardEmoji { + type + awardEmoji { + nodes { + ...AwardEmojiFragment + } + } + } } diff --git a/app/assets/javascripts/work_items/utils.js b/app/assets/javascripts/work_items/utils.js index c5b937ef132..653819904af 100644 --- a/app/assets/javascripts/work_items/utils.js +++ b/app/assets/javascripts/work_items/utils.js @@ -1,4 +1,14 @@ -import { WIDGET_TYPE_HIERARCHY } from '~/work_items/constants'; +import { uniqueId } from 'lodash'; +import { + WIDGET_TYPE_HIERARCHY, + WIDGET_TYPE_CURRENT_USER_TODOS, + CURRENT_USER_TODOS_TYPENAME, + TODO_CONNECTION_TYPENAME, + TODO_EDGE_TYPENAME, + TODO_TYPENAME, + WORK_ITEM_TYPENAME, + WORK_ITEM_UPDATE_PAYLOAD_TYPENAME, +} from '~/work_items/constants'; import workItemQuery from './graphql/work_item.query.graphql'; import workItemByIidQuery from './graphql/work_item_by_iid.query.graphql'; @@ -28,3 +38,38 @@ export const markdownPreviewPath = (fullPath, iid) => `${ gon.relative_url_root || '' }/${fullPath}/preview_markdown?target_type=WorkItem&target_id=${iid}`; + +export const getWorkItemTodoOptimisticResponse = ({ workItem, pendingTodo }) => { + const todo = pendingTodo + ? [ + { + node: { + id: -uniqueId(), + state: 'pending', + __typename: TODO_TYPENAME, + }, + __typename: TODO_EDGE_TYPENAME, + }, + ] + : []; + return { + workItemUpdate: { + errors: [], + workItem: { + ...workItem, + widgets: [ + { + type: WIDGET_TYPE_CURRENT_USER_TODOS, + currentUserTodos: { + edges: todo, + __typename: TODO_CONNECTION_TYPENAME, + }, + __typename: CURRENT_USER_TODOS_TYPENAME, + }, + ], + __typename: WORK_ITEM_TYPENAME, + }, + __typename: WORK_ITEM_UPDATE_PAYLOAD_TYPENAME, + }, + }; +}; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 56ef6d13252..f828129cdf1 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -36,7 +36,7 @@ } .right-sidebar-collapsed { - --application-bar-right: #{$gutter-collapsed-width}; + --application-bar-right: #{$right-sidebar-collapsed-width}; &.is-merge-request { --application-bar-right: 0px; @@ -44,7 +44,7 @@ } .right-sidebar-expanded { - --application-bar-right: #{$gutter-width}; + --application-bar-right: #{$right-sidebar-width}; } } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index f2486640650..9fdf889f4e9 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -22,7 +22,7 @@ &:not(.is-merge-request) { @include media-breakpoint-up(sm) { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { - padding-right: $gutter-collapsed-width; + padding-right: $right-sidebar-collapsed-width; } } } @@ -30,7 +30,7 @@ &.is-merge-request { @include media-breakpoint-up(lg) { .content-wrapper { - padding-right: $gutter-collapsed-width; + padding-right: $right-sidebar-collapsed-width; } } } @@ -56,7 +56,7 @@ z-index: $zindex-dropdown-menu; &.right-sidebar-merge-requests { - width: $gutter-width; + width: $right-sidebar-width; @include media-breakpoint-up(md) { z-index: auto; @@ -69,14 +69,14 @@ @include media-breakpoint-only(sm) { &:not(.wiki-sidebar):not(.build-sidebar):not(.issuable-bulk-update-sidebar) .content-wrapper { - padding-right: $gutter-collapsed-width; + padding-right: $right-sidebar-collapsed-width; } } &:not(.is-merge-request) { @include media-breakpoint-up(md) { .content-wrapper { - padding-right: $gutter-width; + padding-right: $right-sidebar-width; } } } @@ -102,7 +102,7 @@ @mixin maintain-sidebar-dimensions { display: block; - width: $gutter-width; + width: $right-sidebar-width; } .issues-bulk-update.right-sidebar { @@ -113,7 +113,7 @@ &.right-sidebar-expanded { @include maintain-sidebar-dimensions; - width: $gutter-width; + width: $right-sidebar-width; .issuable-sidebar-header { // matches `.top-area .nav-controls` for issuable index pages @@ -401,7 +401,7 @@ border-bottom: 1px solid $border-gray-normal; // This prevents the mess when resizing the sidebar // of elements repositioning themselves.. - width: $gutter-inner-width; + width: $right-sidebar-inner-width; // -- &:last-child { @@ -474,7 +474,7 @@ &.right-sidebar-expanded { &:not(.right-sidebar-merge-requests) { - width: $gutter-width; + width: $right-sidebar-width; } .value { @@ -554,13 +554,13 @@ } } - width: $gutter-collapsed-width; + width: $right-sidebar-collapsed-width; padding: 0; .block, .sidebar-contained-width, .issuable-sidebar-header { - width: $gutter-collapsed-width - 2px; + width: $right-sidebar-collapsed-width - 2px; padding: 0; border-bottom: 0; overflow: hidden; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 6cf231fbcef..dc6a5c5479c 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -2,9 +2,9 @@ * Layout */ $grid-size: 8px; -$gutter-collapsed-width: 62px; -$gutter-width: 290px; -$gutter-inner-width: 250px; +$right-sidebar-collapsed-width: 62px; +$right-sidebar-width: 290px; +$right-sidebar-inner-width: 250px; $sidebar-breakpoint: 1024px; $default-transition-duration: 0.15s; $contextual-sidebar-width: 256px; diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss index 32a6af0dc36..5aca697ae26 100644 --- a/app/assets/stylesheets/page_bundles/boards.scss +++ b/app/assets/stylesheets/page_bundles/boards.scss @@ -15,7 +15,7 @@ width: 100%; &.is-compact { - width: calc(100% - #{$gutter-width}); + width: calc(100% - #{$right-sidebar-width}); } } } diff --git a/app/assets/stylesheets/page_bundles/login.scss b/app/assets/stylesheets/page_bundles/login.scss index 1ae7230772d..6175bba6ba7 100644 --- a/app/assets/stylesheets/page_bundles/login.scss +++ b/app/assets/stylesheets/page_bundles/login.scss @@ -292,3 +292,9 @@ } } } + +@include media-breakpoint-down(sm) { + .sm-bg-gray-10 { + @include gl-bg-gray-10; + } +} diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index 17ca7828a20..b282d83b74b 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -964,7 +964,7 @@ $tabs-holder-z-index: 250; .merge-request-overview { @include media-breakpoint-up(lg) { display: grid; - grid-template-columns: calc(97% - #{$gutter-width}) auto; + grid-template-columns: calc(97% - #{$right-sidebar-width}) auto; grid-gap: 3%; } } @@ -1131,7 +1131,7 @@ $tabs-holder-z-index: 250; width: 100%; height: $toggle-sidebar-height; padding-left: $contextual-sidebar-width; - padding-right: $gutter_collapsed_width; + padding-right: $right-sidebar-collapsed-width; background: var(--white, $white); border-top: 1px solid var(--border-color, $border-color); transition: padding $gl-transition-duration-medium; |