diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-13 18:08:58 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-10-13 18:08:58 +0000 |
commit | b4b6bff01d33ddf1ebd78001f16027b3ccd6443e (patch) | |
tree | 772dfdf23d6e72980bc99f53fa064ec5fb3a2468 /app | |
parent | 16515bdfcb89ccb28e6eb81020d1646dfa7c6fa4 (diff) | |
download | gitlab-ce-b4b6bff01d33ddf1ebd78001f16027b3ccd6443e.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
19 files changed, 572 insertions, 418 deletions
diff --git a/app/assets/javascripts/behaviors/gl_emoji.js b/app/assets/javascripts/behaviors/gl_emoji.js index bcf732e9522..16373b523b2 100644 --- a/app/assets/javascripts/behaviors/gl_emoji.js +++ b/app/assets/javascripts/behaviors/gl_emoji.js @@ -3,9 +3,7 @@ import isEmojiUnicodeSupported from '../emoji/support'; import { initEmojiMap, getEmojiInfo, emojiFallbackImageSrc, emojiImageTag } from '../emoji'; class GlEmoji extends HTMLElement { - constructor() { - super(); - + connectedCallback() { this.initialize(); } initialize() { diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 480c73fda0d..b08b9df13a4 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -9,7 +9,6 @@ import { GlButtonGroup, GlDropdown, GlDropdownItem, - GlDropdownSectionHeader, GlDropdownDivider, } from '@gitlab/ui'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; @@ -19,6 +18,7 @@ import { __, s__, sprintf } from '~/locale'; import { diffViewerModes } from '~/ide/constants'; import DiffStats from './diff_stats.vue'; import { scrollToElement } from '~/lib/utils/common_utils'; +import { DIFF_FILE_HEADER } from '../i18n'; export default { components: { @@ -30,13 +30,15 @@ export default { GlButtonGroup, GlDropdown, GlDropdownItem, - GlDropdownSectionHeader, GlDropdownDivider, }, directives: { GlTooltip: GlTooltipDirective, SafeHtml: GlSafeHtmlDirective, }, + i18n: { + ...DIFF_FILE_HEADER, + }, props: { discussionPath: { type: String, @@ -290,7 +292,7 @@ export default { icon="external-link" /> <gl-dropdown - v-gl-tooltip.hover.focus="__('More actions')" + v-gl-tooltip.hover.focus="$options.i18n.optionsDropdownTitle" right toggle-class="btn-icon js-diff-more-actions" class="gl-pt-0!" @@ -299,11 +301,8 @@ export default { > <template #button-content> <gl-icon name="ellipsis_v" class="mr-0" /> - <span class="sr-only">{{ __('More actions') }}</span> + <span class="sr-only">{{ $options.i18n.optionsDropdownTitle }}</span> </template> - <gl-dropdown-section-header> - {{ __('More actions') }} - </gl-dropdown-section-header> <gl-dropdown-item v-if="diffFile.replaced_view_path" ref="replacedFileButton" diff --git a/app/assets/javascripts/diffs/i18n.js b/app/assets/javascripts/diffs/i18n.js new file mode 100644 index 00000000000..8699cd88a18 --- /dev/null +++ b/app/assets/javascripts/diffs/i18n.js @@ -0,0 +1,5 @@ +import { __ } from '~/locale'; + +export const DIFF_FILE_HEADER = { + optionsDropdownTitle: __('Options'), +}; diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js b/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js index 17dc370df7b..b7b695376e1 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js @@ -14,14 +14,11 @@ import { createUniqueJobId } from '../../utils'; export const generateLinksData = ({ links }, jobs, containerID) => { const containerEl = document.getElementById(containerID); - return links.map(link => { const path = d3.path(); - // We can only have one unique job name per stage, so our selector - // is: ${stageName}-${jobName} - const sourceId = createUniqueJobId(jobs[link.source].stage, link.source); - const targetId = createUniqueJobId(jobs[link.target].stage, link.target); + const sourceId = jobs[link.source].id; + const targetId = jobs[link.target].id; const sourceNodeEl = document.getElementById(sourceId); const targetNodeEl = document.getElementById(targetId); @@ -80,6 +77,12 @@ export const generateLinksData = ({ links }, jobs, containerID) => { targetNodeY, ); - return { ...link, path: path.toString() }; + return { + ...link, + source: sourceId, + target: targetId, + ref: createUniqueJobId(sourceId, targetId), + path: path.toString(), + }; }); }; diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue index d7197cd0585..8eec0110865 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue @@ -14,6 +14,42 @@ export default { type: String, required: true, }, + isHighlighted: { + type: Boolean, + required: false, + default: false, + }, + isFadedOut: { + type: Boolean, + required: false, + default: false, + }, + handleMouseOver: { + type: Function, + required: false, + default: () => {}, + }, + handleMouseLeave: { + type: Function, + required: false, + default: () => {}, + }, + }, + computed: { + jobPillClasses() { + return [ + { 'gl-opacity-3': this.isFadedOut }, + this.isHighlighted ? 'gl-shadow-blue-200-x0-y0-b4-s2' : 'gl-inset-border-2-green-400', + ]; + }, + }, + methods: { + onMouseEnter() { + this.$emit('on-mouse-enter', this.jobId); + }, + onMouseLeave() { + this.$emit('on-mouse-leave'); + }, }, }; </script> @@ -21,7 +57,10 @@ export default { <tooltip-on-truncate :title="jobName" truncate-target="child" placement="top"> <div :id="jobId" - class="gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-inset-border-1-green-600 gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 pipeline-job-pill " + class="pipeline-job-pill gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" + :class="jobPillClasses" + @mouseover="onMouseEnter" + @mouseleave="onMouseLeave" > {{ jobName }} </div> diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue index 816f65c8478..cbde2afbd94 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue @@ -7,7 +7,7 @@ import StagePill from './stage_pill.vue'; import { generateLinksData } from './drawing_utils'; import { parseData } from '../parsing_utils'; import { DRAW_FAILURE, DEFAULT } from '../../constants'; -import { createUniqueJobId } from '../../utils'; +import { generateJobNeedsDict } from '../../utils'; export default { components: { @@ -31,7 +31,9 @@ export default { data() { return { failureType: null, + highlightedJob: null, links: [], + needsObject: null, height: 0, width: 0, }; @@ -43,6 +45,9 @@ export default { hasError() { return this.failureType; }, + hasHighlightedJob() { + return Boolean(this.highlightedJob); + }, failure() { const text = this.$options.errorTexts[this.failureType] || this.$options.errorTexts[DEFAULT]; @@ -51,8 +56,27 @@ export default { viewBox() { return [0, 0, this.width, this.height]; }, - lineStyle() { - return `stroke-width:${this.$options.STROKE_WIDTH}px;`; + highlightedJobs() { + // If you are hovering on a job, then the jobs we want to highlight are: + // The job you are currently hovering + all of its needs. + return this.hasHighlightedJob + ? [this.highlightedJob, ...this.needsObject[this.highlightedJob]] + : []; + }, + highlightedLinks() { + // If you are hovering on a job, then the links we want to highlight are: + // All the links whose `source` and `target` are highlighted jobs. + if (this.hasHighlightedJob) { + const filteredLinks = this.links.filter(link => { + return ( + this.highlightedJobs.includes(link.source) && this.highlightedJobs.includes(link.target) + ); + }); + + return filteredLinks.map(link => link.ref); + } + + return []; }, }, mounted() { @@ -62,9 +86,6 @@ export default { } }, methods: { - createJobId(stageName, jobName) { - return createUniqueJobId(stageName, jobName); - }, drawJobLinks() { const { stages, jobs } = this.pipelineData; const unwrappedGroups = this.unwrapPipelineData(stages); @@ -76,6 +97,18 @@ export default { this.reportFailure(DRAW_FAILURE); } }, + highlightNeeds(uniqueJobId) { + // The first time we hover, we create the object where + // we store all the data to properly highlight the needs. + if (!this.needsObject) { + this.needsObject = generateJobNeedsDict(this.pipelineData) ?? {}; + } + + this.highlightedJob = uniqueJobId; + }, + removeHighlightNeeds() { + this.highlightedJob = null; + }, unwrapPipelineData(stages) { return stages .map(({ name, groups }) => { @@ -95,6 +128,18 @@ export default { resetFailure() { this.failureType = null; }, + isJobHighlighted(jobName) { + return this.highlightedJobs.includes(jobName); + }, + isLinkHighlighted(linkRef) { + return this.highlightedLinks.includes(linkRef); + }, + getLinkClasses(link) { + return [ + this.isLinkHighlighted(link.ref) ? 'gl-stroke-blue-400' : 'gl-stroke-gray-200', + { 'gl-opacity-3': this.hasHighlightedJob && !this.isLinkHighlighted(link.ref) }, + ]; + }, }, }; </script> @@ -113,13 +158,17 @@ export default { class="gl-display-flex gl-bg-gray-50 gl-px-4 gl-overflow-auto gl-relative gl-py-7" > <svg :viewBox="viewBox" :width="width" :height="height" class="gl-absolute"> - <path - v-for="link in links" - :key="link.path" - :d="link.path" - class="gl-stroke-gray-200 gl-fill-transparent" - :style="lineStyle" - /> + <template> + <path + v-for="link in links" + :key="link.path" + :ref="link.ref" + :d="link.path" + class="gl-fill-transparent gl-transition-duration-slow gl-transition-timing-function-ease" + :class="getLinkClasses(link)" + :stroke-width="$options.STROKE_WIDTH" + /> + </template> </svg> <div v-for="(stage, index) in pipelineData.stages" @@ -141,8 +190,12 @@ export default { <job-pill v-for="group in stage.groups" :key="group.name" - :job-id="createJobId(stage.name, group.name)" + :job-id="group.id" :job-name="group.name" + :is-highlighted="hasHighlightedJob && isJobHighlighted(group.id)" + :is-faded-out="hasHighlightedJob && !isJobHighlighted(group.id)" + @on-mouse-enter="highlightNeeds" + @on-mouse-leave="removeHighlightNeeds" /> </div> </div> diff --git a/app/assets/javascripts/pipelines/utils.js b/app/assets/javascripts/pipelines/utils.js index c15e5db55a4..7d1a1762e0d 100644 --- a/app/assets/javascripts/pipelines/utils.js +++ b/app/assets/javascripts/pipelines/utils.js @@ -5,6 +5,8 @@ export const validateParams = params => { return pickBy(params, (val, key) => SUPPORTED_FILTER_PARAMETERS.includes(key) && val); }; +export const createUniqueJobId = (stageName, jobName) => `${stageName}-${jobName}`; + /** * This function takes a json payload that comes from a yml * file converted to json through `jsyaml` library. Because we @@ -21,7 +23,10 @@ export const preparePipelineGraphData = jsonData => { // Creates an object with only the valid jobs const jobs = jsonKeys.reduce((acc, val) => { if (jobNames.includes(val)) { - return { ...acc, [val]: { ...jsonData[val] } }; + return { + ...acc, + [val]: { ...jsonData[val], id: createUniqueJobId(jsonData[val].stage, val) }, + }; } return { ...acc }; }, {}); @@ -47,7 +52,11 @@ export const preparePipelineGraphData = jsonData => { return { name: stage, groups: stageJobs.map(job => { - return { name: job, jobs: [{ ...jsonData[job] }] }; + return { + name: job, + jobs: [{ ...jsonData[job] }], + id: createUniqueJobId(stage, job), + }; }), }; }); @@ -55,4 +64,33 @@ export const preparePipelineGraphData = jsonData => { return { stages: pipelineData, jobs }; }; -export const createUniqueJobId = (stageName, jobName) => `${stageName}-${jobName}`; +export const generateJobNeedsDict = ({ jobs }) => { + const arrOfJobNames = Object.keys(jobs); + + return arrOfJobNames.reduce((acc, value) => { + const recursiveNeeds = jobName => { + if (!jobs[jobName]?.needs) { + return []; + } + + return jobs[jobName].needs + .map(job => { + const { id } = jobs[job]; + // If we already have the needs of a job in the accumulator, + // then we use the memoized data instead of the recursive call + // to save some performance. + const newNeeds = acc[id] ?? recursiveNeeds(job); + + return [id, ...newNeeds]; + }) + .flat(Infinity); + }; + + // To ensure we don't have duplicates job relationship when 2 jobs + // needed by another both depends on the same jobs, we remove any + // duplicates from the array. + const uniqueValues = Array.from(new Set(recursiveNeeds(value))); + + return { ...acc, [jobs[value].id]: uniqueValues }; + }, {}); +}; diff --git a/app/assets/javascripts/snippets/components/snippet_blob_edit.vue b/app/assets/javascripts/snippets/components/snippet_blob_edit.vue index f21456789ac..6a10dc38f2c 100644 --- a/app/assets/javascripts/snippets/components/snippet_blob_edit.vue +++ b/app/assets/javascripts/snippets/components/snippet_blob_edit.vue @@ -1,7 +1,7 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; import BlobHeaderEdit from '~/blob/components/blob_edit_header.vue'; -import BlobContentEdit from '~/blob/components/blob_edit_content.vue'; +import EditorLite from '~/vue_shared/components/editor_lite.vue'; import { getBaseURL, joinPaths } from '~/lib/utils/url_utility'; import axios from '~/lib/utils/axios_utils'; import { SNIPPET_BLOB_CONTENT_FETCH_ERROR } from '~/snippets/constants'; @@ -11,8 +11,8 @@ import { sprintf } from '~/locale'; export default { components: { BlobHeaderEdit, - BlobContentEdit, GlLoadingIcon, + EditorLite, }, inheritAttrs: false, props: { @@ -85,7 +85,7 @@ export default { size="lg" class="loading-animation prepend-top-20 gl-mb-6" /> - <blob-content-edit + <editor-lite v-else :value="blob.content" :file-global-id="blob.id" diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js index 37ebe6b6c4d..c1521882682 100644 --- a/app/assets/javascripts/tracking.js +++ b/app/assets/javascripts/tracking.js @@ -9,7 +9,7 @@ const DEFAULT_SNOWPLOW_OPTIONS = { respectDoNotTrack: true, forceSecureTracker: true, eventMethod: 'post', - contexts: { webPage: true }, + contexts: { webPage: true, performanceTiming: true }, formTracking: false, linkClickTracking: false, }; diff --git a/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss b/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss index 702df5750ec..499394ad960 100644 --- a/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss +++ b/app/assets/stylesheets/page_bundles/_pipeline_mixins.scss @@ -24,3 +24,210 @@ color: $gl-text-color; } } + +@mixin mini-pipeline-graph-color( + $color-background-default, + $color-background-hover-focus, + $color-background-active, + $color-foreground-default, + $color-foreground-hover-focus, + $color-foreground-active +) { + background-color: $color-background-default; + border-color: $color-foreground-default; + + svg { + fill: $color-foreground-default; + } + + &:hover, + &:focus { + background-color: $color-background-hover-focus; + border-color: $color-foreground-hover-focus; + + svg { + fill: $color-foreground-hover-focus; + } + } + + &:active { + background-color: $color-background-active; + border-color: $color-foreground-active; + + svg { + fill: $color-foreground-active; + } + } + + &:focus { + box-shadow: 0 0 4px 1px $blue-300; + } +} + +@mixin mini-pipeline-item() { + border-radius: 100px; + background-color: $white; + border-width: 1px; + border-style: solid; + width: $ci-action-icon-size; + height: $ci-action-icon-size; + margin: 0; + padding: 0; + position: relative; + vertical-align: middle; + + &:hover, + &:active, + &:focus { + outline: none; + border-width: 2px; + } + + // Dropdown button animation in mini pipeline graph + &.ci-status-icon-success { + @include mini-pipeline-graph-color($white, $green-100, $green-200, $green-500, $green-600, $green-700); + } + + &.ci-status-icon-failed { + @include mini-pipeline-graph-color($white, $red-100, $red-200, $red-500, $red-600, $red-700); + } + + &.ci-status-icon-pending, + &.ci-status-icon-waiting-for-resource, + &.ci-status-icon-success-with-warnings { + @include mini-pipeline-graph-color($white, $orange-50, $orange-100, $orange-500, $orange-600, $orange-700); + } + + &.ci-status-icon-preparing, + &.ci-status-icon-running { + @include mini-pipeline-graph-color($white, $blue-100, $blue-200, $blue-500, $blue-600, $blue-700); + } + + &.ci-status-icon-canceled, + &.ci-status-icon-scheduled, + &.ci-status-icon-disabled, + &.ci-status-icon-not-found, + &.ci-status-icon-manual { + @include mini-pipeline-graph-color($white, $gray-500, $gray-700, $gray-900, $gray-950, $black); + } + + &.ci-status-icon-created, + &.ci-status-icon-skipped { + @include mini-pipeline-graph-color($white, $gray-100, $gray-200, $gray-300, $gray-400, $gray-500); + } +} + +/** + Action icons inside dropdowns: + - mini graph in pipelines table + - dropdown in big graph + - mini graph in MR widget pipeline + - mini graph in Commit widget pipeline +*/ +@mixin pipeline-graph-dropdown-menu() { + width: 240px; + max-width: 240px; + + // override dropdown.scss + &.dropdown-menu li button, + &.dropdown-menu li a.ci-action-icon-container { + padding: 0; + text-align: center; + } + + .ci-action-icon-container { + position: absolute; + right: 8px; + top: 8px; + + &.ci-action-icon-wrapper { + height: $ci-action-dropdown-button-size; + width: $ci-action-dropdown-button-size; + border-radius: 50%; + display: block; + + &:hover { + box-shadow: inset 0 0 0 0.0625rem $dropdown-toggle-active-border-color; + background-color: $gray-darker; + + svg { + fill: $gl-text-color; + } + } + + .spinner, + svg { + width: $ci-action-dropdown-svg-size; + height: $ci-action-dropdown-svg-size; + fill: $gl-text-color-secondary; + position: relative; + top: 1px; + vertical-align: initial; + } + } + } + + // SVGs in the commit widget and mr widget + a.ci-action-icon-container.ci-action-icon-wrapper svg { + top: 5px; + } + + .scrollable-menu { + padding: 0; + max-height: 245px; + overflow: auto; + } + + li { + position: relative; + + // ensure .mini-pipeline-graph-dropdown-item has hover style when action-icon is hovered + &:hover > .mini-pipeline-graph-dropdown-item, + &:hover > .ci-job-component > .mini-pipeline-graph-dropdown-item { + @extend .mini-pipeline-graph-dropdown-item:hover; + } + + // link to the build + .mini-pipeline-graph-dropdown-item { + align-items: center; + clear: both; + display: flex; + font-weight: normal; + line-height: $line-height-base; + white-space: nowrap; + + // Match dropdown.scss for all `a` tags + &.non-details-job-component { + padding: $gl-padding-8 $gl-btn-horz-padding; + } + + .ci-job-name-component { + align-items: center; + display: flex; + flex: 1; + } + + .ci-status-icon { + @include gl-mr-3; + + position: relative; + + > svg { + width: $pipeline-dropdown-status-icon-size; + height: $pipeline-dropdown-status-icon-size; + margin: 3px 0; + position: relative; + overflow: visible; + display: block; + } + } + + &:hover, + &:focus { + outline: none; + text-decoration: none; + background-color: $gray-darker; + } + } + } +} diff --git a/app/assets/stylesheets/page_bundles/pipeline.scss b/app/assets/stylesheets/page_bundles/pipeline.scss index 5ba9668ca19..cbabfd1fa50 100644 --- a/app/assets/stylesheets/page_bundles/pipeline.scss +++ b/app/assets/stylesheets/page_bundles/pipeline.scss @@ -386,7 +386,6 @@ top: 8px; } - .split-report-section { border-bottom: 1px solid var(--gray-50, $gray-50); @@ -427,6 +426,43 @@ } } +.big-pipeline-graph-dropdown-menu { + @include pipeline-graph-dropdown-menu(); + width: 195px; + min-width: 195px; + left: 100%; + top: -10px; + box-shadow: 0 1px 5px $black-transparent; + + /** + * Top arrow in the dropdown in the big pipeline graph + */ + &::before, + &::after { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: 18px; + } + + &::before { + left: -6px; + margin-top: 3px; + border-width: 7px 5px 7px 0; + border-right-color: $border-color; + } + + &::after { + left: -5px; + border-width: 10px 7px 10px 0; + border-right-color: $white; + } +} + .codequality-report { .media { padding: $gl-padding; diff --git a/app/assets/stylesheets/page_bundles/pipelines.scss b/app/assets/stylesheets/page_bundles/pipelines.scss index 8fcfde6b32b..6ff07017d2e 100644 --- a/app/assets/stylesheets/page_bundles/pipelines.scss +++ b/app/assets/stylesheets/page_bundles/pipelines.scss @@ -1,14 +1,14 @@ @import 'mixins_and_variables_and_functions'; +@import './pipeline_mixins'; /** - * Pipelines Bundle - * - * Styles of pipeline lists - * - * Should affect pipelines table components rendered by: - * app/assets/javascripts/commit/pipelines/pipelines_bundle.js + * Pipelines Bundle: Pipeline lists and Mini Pipelines */ +// Pipelines list +// Should affect pipelines table components rendered by: +// - app/assets/javascripts/commit/pipelines/pipelines_bundle.js + .pipelines { .badge { margin-bottom: 3px; @@ -64,3 +64,132 @@ white-space: normal; } } + +// Mini Pipelines + +.stage-cell { + .mini-pipeline-graph-dropdown-toggle { + svg { + height: $ci-action-icon-size; + width: $ci-action-icon-size; + position: absolute; + top: -1px; + left: -1px; + z-index: 2; + overflow: visible; + } + + &:hover, + &:active, + &:focus { + svg { + top: -2px; + left: -2px; + } + } + } + + .stage-container { + display: inline-block; + position: relative; + vertical-align: middle; + height: $ci-action-icon-size; + margin: 3px 0; + + + .stage-container { + margin-left: 6px; + } + + // Hack to show a button tooltip inline + button.has-tooltip + .tooltip { + min-width: 105px; + } + + // Bootstrap way of showing the content inline for anchors. + a.has-tooltip { + white-space: nowrap; + } + + &:not(:last-child) { + &::after { + content: ''; + width: 7px; + position: absolute; + right: -7px; + top: 11px; + border-bottom: 2px solid $border-color; + } + } + + //delete when all pipelines are updated to new size + &.mr-widget-pipeline-stages { + + .stage-container { + margin-left: 4px; + } + + &:not(:last-child) { + &::after { + width: 4px; + right: -4px; + top: 11px; + } + } + } + } +} + +// Dropdown button in mini pipeline graph +button.mini-pipeline-graph-dropdown-toggle { + @include mini-pipeline-item(); +} + +// Action icons inside dropdowns: +// mini graph in pipelines table +// mini graph in MR widget pipeline +// mini graph in Commit widget pipeline +.mini-pipeline-graph-dropdown-menu { + @include pipeline-graph-dropdown-menu(); + + &::before, + &::after { + content: ''; + display: inline-block; + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; + top: -6px; + left: 50%; + transform: translate(-50%, 0); + border-width: 0 5px 6px; + + @include media-breakpoint-down(sm) { + left: 100%; + margin-left: -12px; + } + } + + &::before { + border-width: 0 5px 5px; + border-bottom-color: $border-color; + } + + &::after { + margin-top: 1px; + border-bottom-color: $white; + } + + /** + * Center dropdown menu in mini graph + */ + .dropdown &.dropdown-menu { + transform: translate(-80%, 0); + + @media (min-width: map-get($grid-breakpoints, md)) { + transform: translate(-50%, 0); + right: auto; + left: 50%; + } + } +} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index b758e2c588c..5e7222b52eb 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -78,77 +78,6 @@ } } -.stage-cell { - .mini-pipeline-graph-dropdown-toggle { - svg { - height: $ci-action-icon-size; - width: $ci-action-icon-size; - position: absolute; - top: -1px; - left: -1px; - z-index: 2; - overflow: visible; - } - - &:hover, - &:active, - &:focus { - svg { - top: -2px; - left: -2px; - } - } - } - - .stage-container { - display: inline-block; - position: relative; - vertical-align: middle; - height: $ci-action-icon-size; - margin: 3px 0; - - + .stage-container { - margin-left: 6px; - } - - // Hack to show a button tooltip inline - button.has-tooltip + .tooltip { - min-width: 105px; - } - - // Bootstrap way of showing the content inline for anchors. - a.has-tooltip { - white-space: nowrap; - } - - &:not(:last-child) { - &::after { - content: ''; - width: 7px; - position: absolute; - right: -7px; - top: 11px; - border-bottom: 2px solid $border-color; - } - } - - //delete when all pipelines are updated to new size - &.mr-widget-pipeline-stages { - + .stage-container { - margin-left: 4px; - } - - &:not(:last-child) { - &::after { - width: 4px; - right: -4px; - top: 11px; - } - } - } - } -} - [data-page='admin:jobs:index'] { .admin-builds-table { td:last-child { @@ -162,305 +91,6 @@ font-weight: 200; } -@mixin mini-pipeline-graph-color( - $color-background-default, - $color-background-hover-focus, - $color-background-active, - $color-foreground-default, - $color-foreground-hover-focus, - $color-foreground-active -) { - background-color: $color-background-default; - border-color: $color-foreground-default; - - svg { - fill: $color-foreground-default; - } - - &:hover, - &:focus { - background-color: $color-background-hover-focus; - border-color: $color-foreground-hover-focus; - - svg { - fill: $color-foreground-hover-focus; - } - } - - &:active { - background-color: $color-background-active; - border-color: $color-foreground-active; - - svg { - fill: $color-foreground-active; - } - } - - &:focus { - box-shadow: 0 0 4px 1px $blue-300; - } -} - -@mixin mini-pipeline-item() { - border-radius: 100px; - background-color: $white; - border-width: 1px; - border-style: solid; - width: $ci-action-icon-size; - height: $ci-action-icon-size; - margin: 0; - padding: 0; - position: relative; - vertical-align: middle; - - &:hover, - &:active, - &:focus { - outline: none; - border-width: 2px; - } - - // Dropdown button animation in mini pipeline graph - &.ci-status-icon-success { - @include mini-pipeline-graph-color($white, $green-100, $green-200, $green-500, $green-600, $green-700); - } - - &.ci-status-icon-failed { - @include mini-pipeline-graph-color($white, $red-100, $red-200, $red-500, $red-600, $red-700); - } - - &.ci-status-icon-pending, - &.ci-status-icon-waiting-for-resource, - &.ci-status-icon-success-with-warnings { - @include mini-pipeline-graph-color($white, $orange-50, $orange-100, $orange-500, $orange-600, $orange-700); - } - - &.ci-status-icon-preparing, - &.ci-status-icon-running { - @include mini-pipeline-graph-color($white, $blue-100, $blue-200, $blue-500, $blue-600, $blue-700); - } - - &.ci-status-icon-canceled, - &.ci-status-icon-scheduled, - &.ci-status-icon-disabled, - &.ci-status-icon-not-found, - &.ci-status-icon-manual { - @include mini-pipeline-graph-color($white, $gray-500, $gray-700, $gray-900, $gray-950, $black); - } - - &.ci-status-icon-created, - &.ci-status-icon-skipped { - @include mini-pipeline-graph-color($white, $gray-100, $gray-200, $gray-300, $gray-400, $gray-500); - } -} - -// Dropdown button in mini pipeline graph -button.mini-pipeline-graph-dropdown-toggle { - @include mini-pipeline-item(); -} - -/** - Action icons inside dropdowns: - - mini graph in pipelines table - - dropdown in big graph - - mini graph in MR widget pipeline - - mini graph in Commit widget pipeline -*/ -.big-pipeline-graph-dropdown-menu, -.mini-pipeline-graph-dropdown-menu { - width: 240px; - max-width: 240px; - - // override dropdown.scss - &.dropdown-menu li button, - &.dropdown-menu li a.ci-action-icon-container { - padding: 0; - text-align: center; - } - - .ci-action-icon-container { - position: absolute; - right: 8px; - top: 8px; - - &.ci-action-icon-wrapper { - height: $ci-action-dropdown-button-size; - width: $ci-action-dropdown-button-size; - border-radius: 50%; - display: block; - - &:hover { - box-shadow: inset 0 0 0 0.0625rem $dropdown-toggle-active-border-color; - background-color: $gray-darker; - - svg { - fill: $gl-text-color; - } - } - - .spinner, - svg { - width: $ci-action-dropdown-svg-size; - height: $ci-action-dropdown-svg-size; - fill: $gl-text-color-secondary; - position: relative; - top: 1px; - vertical-align: initial; - } - } - } - - // SVGs in the commit widget and mr widget - a.ci-action-icon-container.ci-action-icon-wrapper svg { - top: 5px; - } - - .scrollable-menu { - padding: 0; - max-height: 245px; - overflow: auto; - } - - li { - position: relative; - - // ensure .mini-pipeline-graph-dropdown-item has hover style when action-icon is hovered - &:hover > .mini-pipeline-graph-dropdown-item, - &:hover > .ci-job-component > .mini-pipeline-graph-dropdown-item { - @extend .mini-pipeline-graph-dropdown-item:hover; - } - - // link to the build - .mini-pipeline-graph-dropdown-item { - align-items: center; - clear: both; - display: flex; - font-weight: normal; - line-height: $line-height-base; - white-space: nowrap; - - // Match dropdown.scss for all `a` tags - &.non-details-job-component { - padding: $gl-padding-8 $gl-btn-horz-padding; - } - - .ci-job-name-component { - align-items: center; - display: flex; - flex: 1; - } - - - .ci-status-icon { - @include gl-mr-3; - - position: relative; - - > svg { - width: $pipeline-dropdown-status-icon-size; - height: $pipeline-dropdown-status-icon-size; - margin: 3px 0; - position: relative; - overflow: visible; - display: block; - } - } - - &:hover, - &:focus { - outline: none; - text-decoration: none; - background-color: $gray-darker; - } - } - } -} - -// Dropdown in the big pipeline graph -.big-pipeline-graph-dropdown-menu { - width: 195px; - min-width: 195px; - left: 100%; - top: -10px; - box-shadow: 0 1px 5px $black-transparent; - - /** - * Top arrow in the dropdown in the big pipeline graph - */ - &::before, - &::after { - content: ''; - display: inline-block; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: 18px; - } - - &::before { - left: -6px; - margin-top: 3px; - border-width: 7px 5px 7px 0; - border-right-color: $border-color; - } - - &::after { - left: -5px; - border-width: 10px 7px 10px 0; - border-right-color: $white; - } -} - -/** - * Top arrow in the dropdown in the mini pipeline graph - */ -.mini-pipeline-graph-dropdown-menu { - &::before, - &::after { - content: ''; - display: inline-block; - position: absolute; - width: 0; - height: 0; - border-color: transparent; - border-style: solid; - top: -6px; - left: 50%; - transform: translate(-50%, 0); - border-width: 0 5px 6px; - - @include media-breakpoint-down(sm) { - left: 100%; - margin-left: -12px; - } - } - - &::before { - border-width: 0 5px 5px; - border-bottom-color: $border-color; - } - - &::after { - margin-top: 1px; - border-bottom-color: $white; - } - - /** - * Center dropdown menu in mini graph - */ - .dropdown &.dropdown-menu { - transform: translate(-80%, 0); - - @media (min-width: map-get($grid-breakpoints, md)) { - transform: translate(-50%, 0); - right: auto; - left: 50%; - } - } -} - /** * Terminal */ diff --git a/app/controllers/projects/static_site_editor_controller.rb b/app/controllers/projects/static_site_editor_controller.rb index 036c731f480..7e2e32a843f 100644 --- a/app/controllers/projects/static_site_editor_controller.rb +++ b/app/controllers/projects/static_site_editor_controller.rb @@ -27,6 +27,8 @@ class Projects::StaticSiteEditorController < Projects::ApplicationController ).execute if service_response.success? + Gitlab::UsageDataCounters::StaticSiteEditorCounter.increment_views_count + @data = serialize_necessary_payload_values_to_json(service_response.payload) else # TODO: For now, if the service returns any error, the user is redirected diff --git a/app/graphql/resolvers/concerns/resolves_merge_requests.rb b/app/graphql/resolvers/concerns/resolves_merge_requests.rb index 819f2ad772a..ab83476ddea 100644 --- a/app/graphql/resolvers/concerns/resolves_merge_requests.rb +++ b/app/graphql/resolvers/concerns/resolves_merge_requests.rb @@ -44,6 +44,7 @@ module ResolvesMergeRequests author: [:author], merged_at: [:metrics], commit_count: [:metrics], + diff_stats_summary: [:metrics], approved_by: [:approved_by_users], milestone: [:milestone], head_pipeline: [:merge_request_diff, { head_pipeline: [:merge_request] }] diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index adb689baf6a..342cda22866 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -170,6 +170,12 @@ module Types end def diff_stats_summary + metrics = object.metrics + + if metrics && metrics.added_lines && metrics.removed_lines + return { additions: metrics.added_lines, deletions: metrics.removed_lines, file_count: object.merge_request_diff&.files_count || 0 } + end + nil_stats = { additions: 0, deletions: 0, file_count: 0 } return nil_stats unless object.diff_stats.present? diff --git a/app/services/jira/requests/base.rb b/app/services/jira/requests/base.rb index 7c6db372257..4ed8df0f235 100644 --- a/app/services/jira/requests/base.rb +++ b/app/services/jira/requests/base.rb @@ -40,7 +40,12 @@ module Jira build_service_response(response) rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, Errno::ECONNREFUSED, URI::InvalidURIError, JIRA::HTTPError, OpenSSL::SSL::SSLError => error error_message = "Jira request error: #{error.message}" - log_error("Error sending message", client_url: client.options[:site], error: error_message) + log_error("Error sending message", client_url: client.options[:site], + error: { + exception_class: error.class.name, + exception_message: error.message, + exception_backtrace: Gitlab::BacktraceCleaner.clean_backtrace(error.backtrace) + }) ServiceResponse.error(message: error_message) end diff --git a/app/services/packages/composer/composer_json_service.rb b/app/services/packages/composer/composer_json_service.rb index 6ffb5a77da3..98aabd84d3d 100644 --- a/app/services/packages/composer/composer_json_service.rb +++ b/app/services/packages/composer/composer_json_service.rb @@ -3,6 +3,8 @@ module Packages module Composer class ComposerJsonService + InvalidJson = Class.new(StandardError) + def initialize(project, target) @project, @target = project, target end @@ -20,11 +22,11 @@ module Packages Gitlab::Json.parse(composer_file.data) rescue JSON::ParserError - raise 'Could not parse composer.json file. Invalid JSON.' + raise InvalidJson, 'Could not parse composer.json file. Invalid JSON.' end def composer_file_not_found! - raise 'The file composer.json was not found.' + raise InvalidJson, 'The file composer.json was not found.' end end end diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 40b96ca477e..003a27f4c9a 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -7,6 +7,7 @@ - @content_class = limited_container_width - page_title "#{@commit.title} (#{@commit.short_id})", _('Commits') - page_description @commit.description +- add_page_specific_style 'page_bundles/pipelines' .container-fluid{ class: [limited_container_width, container_class] } = render "commit_box" |