summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-10-13 18:08:58 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-10-13 18:08:58 +0000
commitb4b6bff01d33ddf1ebd78001f16027b3ccd6443e (patch)
tree772dfdf23d6e72980bc99f53fa064ec5fb3a2468 /app
parent16515bdfcb89ccb28e6eb81020d1646dfa7c6fa4 (diff)
downloadgitlab-ce-b4b6bff01d33ddf1ebd78001f16027b3ccd6443e.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/behaviors/gl_emoji.js4
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue13
-rw-r--r--app/assets/javascripts/diffs/i18n.js5
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/drawing_utils.js15
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue41
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue81
-rw-r--r--app/assets/javascripts/pipelines/utils.js44
-rw-r--r--app/assets/javascripts/snippets/components/snippet_blob_edit.vue6
-rw-r--r--app/assets/javascripts/tracking.js2
-rw-r--r--app/assets/stylesheets/page_bundles/_pipeline_mixins.scss207
-rw-r--r--app/assets/stylesheets/page_bundles/pipeline.scss38
-rw-r--r--app/assets/stylesheets/page_bundles/pipelines.scss141
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss370
-rw-r--r--app/controllers/projects/static_site_editor_controller.rb2
-rw-r--r--app/graphql/resolvers/concerns/resolves_merge_requests.rb1
-rw-r--r--app/graphql/types/merge_request_type.rb6
-rw-r--r--app/services/jira/requests/base.rb7
-rw-r--r--app/services/packages/composer/composer_json_service.rb6
-rw-r--r--app/views/projects/commit/show.html.haml1
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"