summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-04 09:08:20 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-04 09:08:20 +0000
commitd80f3cd75e700b6e62910865bfd36734644ffa89 (patch)
treeaa2fa2f2b4385854c13591bef8e74924ef661657 /app
parentbe81c1578d65f25edfde8aa550f190b8d3e6d976 (diff)
downloadgitlab-ce-d80f3cd75e700b6e62910865bfd36734644ffa89.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue85
-rw-r--r--app/assets/javascripts/blob/suggest_gitlab_ci_yml/index.js16
-rw-r--r--app/assets/javascripts/commons/polyfills.js19
-rw-r--r--app/assets/javascripts/commons/polyfills/custom_event.js7
-rw-r--r--app/assets/javascripts/commons/polyfills/element.js32
-rw-r--r--app/assets/javascripts/commons/polyfills/event.js8
-rw-r--r--app/assets/javascripts/commons/polyfills/nodelist.js7
-rw-r--r--app/assets/javascripts/commons/polyfills/request_idle_callback.js7
-rw-r--r--app/assets/javascripts/commons/polyfills/svg.js10
-rw-r--r--app/assets/javascripts/emoji/index.js4
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js27
-rw-r--r--app/assets/javascripts/monitoring/components/charts/column.vue22
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js3
-rw-r--r--app/assets/javascripts/pages/projects/blob/new/index.js11
-rw-r--r--app/assets/javascripts/pages/projects/releases/show/index.js3
-rw-r--r--app/assets/javascripts/releases/components/app_edit.vue19
-rw-r--r--app/assets/javascripts/releases/components/app_show.vue29
-rw-r--r--app/assets/javascripts/releases/components/release_block_header.vue12
-rw-r--r--app/assets/javascripts/releases/constants.js8
-rw-r--r--app/assets/javascripts/releases/mount_edit.js10
-rw-r--r--app/assets/javascripts/releases/mount_index.js6
-rw-r--r--app/assets/javascripts/releases/mount_show.js21
-rw-r--r--app/assets/javascripts/releases/stores/index.js6
-rw-r--r--app/assets/javascripts/releases/stores/modules/detail/actions.js6
-rw-r--r--app/assets/stylesheets/components/popover.scss12
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/notify.scss16
-rw-r--r--app/assets/stylesheets/pages/projects.scss8
-rw-r--r--app/helpers/markup_helper.rb6
-rw-r--r--app/helpers/suggest_pipeline_helper.rb9
-rw-r--r--app/models/issue.rb4
-rw-r--r--app/models/milestone_note.rb1
-rw-r--r--app/models/resource_event.rb2
-rw-r--r--app/models/resource_label_event.rb4
-rw-r--r--app/views/dashboard/projects/_projects.html.haml2
-rw-r--r--app/views/explore/projects/_projects.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml4
-rw-r--r--app/views/projects/blob/_suggest_gitlab_ci_yml.html.haml4
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml5
-rw-r--r--app/views/projects/runners/_runner.html.haml2
-rw-r--r--app/views/shared/projects/_project.html.haml7
41 files changed, 390 insertions, 77 deletions
diff --git a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue
new file mode 100644
index 00000000000..fa3c19921df
--- /dev/null
+++ b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/components/popover.vue
@@ -0,0 +1,85 @@
+<script>
+import { GlPopover, GlSprintf, GlButton, GlIcon } from '@gitlab/ui';
+import Cookies from 'js-cookie';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import { s__ } from '~/locale';
+import { glEmojiTag } from '~/emoji';
+
+export default {
+ components: {
+ GlPopover,
+ GlSprintf,
+ GlIcon,
+ GlButton,
+ },
+ props: {
+ target: {
+ type: String,
+ required: true,
+ },
+ cssClass: {
+ type: String,
+ required: true,
+ },
+ dismissKey: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ popoverDismissed: parseBoolean(Cookies.get(this.dismissKey)),
+ };
+ },
+ computed: {
+ suggestTitle() {
+ return s__(`suggestPipeline|1/2: Choose a template`);
+ },
+ suggestContent() {
+ return s__(
+ `suggestPipeline|We recommend the %{boldStart}Code Quality%{boldEnd} template, which will add a report widget to your Merge Requests. This way you’ll learn about code quality degradations much sooner. %{footerStart} Goodbye technical debt! %{footerEnd}`,
+ );
+ },
+ emoji() {
+ return glEmojiTag('wave');
+ },
+ },
+ methods: {
+ onDismiss() {
+ this.popoverDismissed = true;
+ Cookies.set(this.dismissKey, this.popoverDismissed, { expires: 365 });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-popover
+ v-if="!popoverDismissed"
+ show
+ :target="target"
+ placement="rightbottom"
+ trigger="manual"
+ container="viewport"
+ :css-classes="[cssClass]"
+ >
+ <template #title>
+ <gl-button :aria-label="__('Close')" class="btn-blank float-right" @click="onDismiss">
+ <gl-icon name="close" aria-hidden="true" />
+ </gl-button>
+ {{ suggestTitle }}
+ </template>
+
+ <gl-sprintf :message="suggestContent">
+ <template #bold="{content}">
+ <strong> {{ content }} </strong>
+ </template>
+ <template #footer="{content}">
+ <div class="mt-3">
+ {{ content }}
+ <span v-html="emoji"></span>
+ </div>
+ </template>
+ </gl-sprintf>
+ </gl-popover>
+</template>
diff --git a/app/assets/javascripts/blob/suggest_gitlab_ci_yml/index.js b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/index.js
new file mode 100644
index 00000000000..f770000eb68
--- /dev/null
+++ b/app/assets/javascripts/blob/suggest_gitlab_ci_yml/index.js
@@ -0,0 +1,16 @@
+import Vue from 'vue';
+import Popover from './components/popover.vue';
+
+export default el =>
+ new Vue({
+ el,
+ render(createElement) {
+ return createElement(Popover, {
+ props: {
+ target: el.dataset.target,
+ cssClass: el.dataset.cssClass,
+ dismissKey: el.dataset.dismissKey,
+ },
+ });
+ },
+ });
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index 5e04b0573d2..fdeb64a7644 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -1,5 +1,24 @@
// Browser polyfills
+
+/**
+ * Polyfill: fetch
+ * @what https://fetch.spec.whatwg.org/
+ * @why Because Apollo GraphQL client relies on fetch
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=fetch
+ */
+import 'unfetch/polyfill/index';
+
+/**
+ * Polyfill: FormData APIs
+ * @what delete(), get(), getAll(), has(), set(), entries(), keys(), values(),
+ * and support for for...of
+ * @why Because Apollo GraphQL client relies on fetch
+ * @browsers Internet Explorer 11, Edge < 18
+ * @see https://caniuse.com/#feat=mdn-api_formdata and subfeatures
+ */
import 'formdata-polyfill';
+
import './polyfills/custom_event';
import './polyfills/element';
import './polyfills/event';
diff --git a/app/assets/javascripts/commons/polyfills/custom_event.js b/app/assets/javascripts/commons/polyfills/custom_event.js
index db51ade61ae..6b14eff6f05 100644
--- a/app/assets/javascripts/commons/polyfills/custom_event.js
+++ b/app/assets/javascripts/commons/polyfills/custom_event.js
@@ -1,3 +1,10 @@
+/**
+ * Polyfill: CustomEvent constructor
+ * @what new CustomEvent()
+ * @why Certain features, e.g. notes utilize this
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=customevent
+ */
if (typeof window.CustomEvent !== 'function') {
window.CustomEvent = function CustomEvent(event, params) {
const evt = document.createEvent('CustomEvent');
diff --git a/app/assets/javascripts/commons/polyfills/element.js b/app/assets/javascripts/commons/polyfills/element.js
index dde5e8f54f9..b13ceccf511 100644
--- a/app/assets/javascripts/commons/polyfills/element.js
+++ b/app/assets/javascripts/commons/polyfills/element.js
@@ -1,6 +1,19 @@
-// polyfill Element.classList and DOMTokenList with classList.js
+/**
+ * Polyfill
+ * @what Element.classList
+ * @why In order to align browser features
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=classlist
+ */
import 'classlist-polyfill';
+/**
+ * Polyfill
+ * @what Element.closest
+ * @why In order to align browser features
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=element-closest
+ */
Element.prototype.closest =
Element.prototype.closest ||
function closest(selector, selectedElement = this) {
@@ -10,6 +23,13 @@ Element.prototype.closest =
: Element.prototype.closest(selector, selectedElement.parentElement);
};
+/**
+ * Polyfill
+ * @what Element.matches
+ * @why In order to align browser features
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=mdn-api_element_matches
+ */
Element.prototype.matches =
Element.prototype.matches ||
Element.prototype.matchesSelector ||
@@ -26,7 +46,15 @@ Element.prototype.matches =
return i > -1;
};
-// From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
+/**
+ * Polyfill
+ * @what ChildNode.remove, Element.remove, CharacterData.remove, DocumentType.remove
+ * @why In order to align browser features
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=childnode-remove
+ *
+ * From the polyfill on MDN, https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
+ */
(arr => {
arr.forEach(item => {
if (Object.prototype.hasOwnProperty.call(item, 'remove')) {
diff --git a/app/assets/javascripts/commons/polyfills/event.js b/app/assets/javascripts/commons/polyfills/event.js
index ff5b9a1982f..543dd5f9a93 100644
--- a/app/assets/javascripts/commons/polyfills/event.js
+++ b/app/assets/javascripts/commons/polyfills/event.js
@@ -1,6 +1,10 @@
/**
- * Polyfill for IE11 support.
- * new Event() is not supported by IE11.
+ * Polyfill: Event constructor
+ * @what new Event()
+ * @why To align browser support
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=mdn-api_event_event
+ *
* Although `initEvent` is deprecated for modern browsers it is the one supported by IE
*/
if (typeof window.Event !== 'function') {
diff --git a/app/assets/javascripts/commons/polyfills/nodelist.js b/app/assets/javascripts/commons/polyfills/nodelist.js
index 3772c94b900..3a9111e64f8 100644
--- a/app/assets/javascripts/commons/polyfills/nodelist.js
+++ b/app/assets/javascripts/commons/polyfills/nodelist.js
@@ -1,3 +1,10 @@
+/**
+ * Polyfill
+ * @what NodeList.forEach
+ * @why To align browser support
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=mdn-api_nodelist_foreach
+ */
if (window.NodeList && !NodeList.prototype.forEach) {
NodeList.prototype.forEach = function forEach(callback, thisArg = window) {
for (let i = 0; i < this.length; i += 1) {
diff --git a/app/assets/javascripts/commons/polyfills/request_idle_callback.js b/app/assets/javascripts/commons/polyfills/request_idle_callback.js
index 2356569d06e..51dc82e593a 100644
--- a/app/assets/javascripts/commons/polyfills/request_idle_callback.js
+++ b/app/assets/javascripts/commons/polyfills/request_idle_callback.js
@@ -1,3 +1,10 @@
+/**
+ * Polyfill
+ * @what requestIdleCallback
+ * @why To align browser features
+ * @browsers Safari (all versions), Internet Explorer 11
+ * @see https://caniuse.com/#feat=requestidlecallback
+ */
window.requestIdleCallback =
window.requestIdleCallback ||
function requestShim(cb) {
diff --git a/app/assets/javascripts/commons/polyfills/svg.js b/app/assets/javascripts/commons/polyfills/svg.js
index 8648a568f6f..92a8b03fbb4 100644
--- a/app/assets/javascripts/commons/polyfills/svg.js
+++ b/app/assets/javascripts/commons/polyfills/svg.js
@@ -1,5 +1,11 @@
+/**
+ * polyfill support for external SVG file references via <use xlink:href>
+ * @what polyfill support for external SVG file references via <use xlink:href>
+ * @why This is used in our GitLab SVG icon library
+ * @browsers Internet Explorer 11
+ * @see https://caniuse.com/#feat=mdn-svg_elements_use_external_uri
+ * @see https//css-tricks.com/svg-use-external-source/
+ */
import svg4everybody from 'svg4everybody';
-// polyfill support for external SVG file references via <use xlink:href>
-// @see https://css-tricks.com/svg-use-external-source/
svg4everybody();
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
index cd8dff40b88..27dff8cf9aa 100644
--- a/app/assets/javascripts/emoji/index.js
+++ b/app/assets/javascripts/emoji/index.js
@@ -1,4 +1,4 @@
-import _ from 'underscore';
+import { uniq } from 'lodash';
import emojiMap from 'emojis/digests.json';
import emojiAliases from 'emojis/aliases.json';
@@ -18,7 +18,7 @@ export function filterEmojiNames(filter) {
}
export function filterEmojiNamesByAlias(filter) {
- return _.uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name)));
+ return uniq(filterEmojiNames(filter).map(name => normalizeEmojiName(name)));
}
let emojiCategoryMap;
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index e9a714605c7..88737396113 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -472,33 +472,6 @@ export default class FilteredSearchManager {
});
input.value = input.value.replace(`${tokenKey}:`, '');
}
-
- const splitSearchToken = searchToken && searchToken.split(' ');
- let lastSearchToken = _.last(splitSearchToken);
- lastSearchToken = lastSearchToken?.toLowerCase();
-
- /**
- * If user writes "milestone", a known token, in the input, we should not
- * wait for leading colon to flush it as a filter token.
- */
- if (this.filteredSearchTokenKeys.getKeys().includes(lastSearchToken)) {
- if (splitSearchToken.length > 1) {
- splitSearchToken.pop();
- const searchVisualTokens = splitSearchToken.join(' ');
-
- input.value = input.value.replace(searchVisualTokens, '');
- FilteredSearchVisualTokens.addSearchVisualToken(searchVisualTokens);
- }
- FilteredSearchVisualTokens.addFilterVisualToken(lastSearchToken, null, null, {
- uppercaseTokenName: this.filteredSearchTokenKeys.shouldUppercaseTokenName(
- lastSearchToken,
- ),
- capitalizeTokenValue: this.filteredSearchTokenKeys.shouldCapitalizeTokenValue(
- lastSearchToken,
- ),
- });
- input.value = input.value.replace(lastSearchToken, '');
- }
} else if (!isLastVisualTokenValid && !FilteredSearchVisualTokens.getLastTokenOperator()) {
const tokenKey = FilteredSearchVisualTokens.getLastTokenPartial();
const tokenOperator = searchToken && searchToken.trim();
diff --git a/app/assets/javascripts/monitoring/components/charts/column.vue b/app/assets/javascripts/monitoring/components/charts/column.vue
index 82857424ff7..0ed801e6e57 100644
--- a/app/assets/javascripts/monitoring/components/charts/column.vue
+++ b/app/assets/javascripts/monitoring/components/charts/column.vue
@@ -5,6 +5,7 @@ import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import { chartHeight } from '../../constants';
import { makeDataSeries } from '~/helpers/monitor_helper';
import { graphDataValidatorForValues } from '../../utils';
+import { getYAxisOptions, getChartGrid } from './options';
export default {
components: {
@@ -41,15 +42,25 @@ export default {
values: queryData[0].data,
};
},
+ chartOptions() {
+ const yAxis = {
+ ...getYAxisOptions(this.graphData.yAxis),
+ scale: false,
+ };
+
+ return {
+ grid: getChartGrid(),
+ yAxis,
+ dataZoom: this.dataZoomConfig,
+ };
+ },
xAxisTitle() {
return this.graphData.metrics[0].result[0].x_label !== undefined
? this.graphData.metrics[0].result[0].x_label
: '';
},
yAxisTitle() {
- return this.graphData.metrics[0].result[0].y_label !== undefined
- ? this.graphData.metrics[0].result[0].y_label
- : '';
+ return this.chartOptions.yAxis.name;
},
xAxisType() {
return this.graphData.x_type !== undefined ? this.graphData.x_type : 'category';
@@ -59,11 +70,6 @@ export default {
return handleIcon ? { handleIcon } : {};
},
- chartOptions() {
- return {
- dataZoom: this.dataZoomConfig,
- };
- },
},
created() {
this.setSvg('scroll-handle');
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index aa6c35d97be..7d0d37c1a20 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -79,7 +79,7 @@ export const fetchData = ({ dispatch }) => {
dispatch('fetchEnvironmentsData');
};
-export const fetchDashboard = ({ state, dispatch }) => {
+export const fetchDashboard = ({ state, commit, dispatch }) => {
dispatch('requestMetricsDashboard');
const params = {};
@@ -100,6 +100,7 @@ export const fetchDashboard = ({ state, dispatch }) => {
.catch(error => {
Sentry.captureException(error);
+ commit(types.SET_ALL_DASHBOARDS, error.response?.data?.all_dashboards ?? []);
dispatch('receiveMetricsDashboardFailure', error);
if (state.showErrorBanner) {
diff --git a/app/assets/javascripts/pages/projects/blob/new/index.js b/app/assets/javascripts/pages/projects/blob/new/index.js
index 189053f3ed7..720cb249052 100644
--- a/app/assets/javascripts/pages/projects/blob/new/index.js
+++ b/app/assets/javascripts/pages/projects/blob/new/index.js
@@ -1,3 +1,12 @@
import initBlobBundle from '~/blob_edit/blob_bundle';
+import initPopover from '~/blob/suggest_gitlab_ci_yml';
-document.addEventListener('DOMContentLoaded', initBlobBundle);
+document.addEventListener('DOMContentLoaded', () => {
+ initBlobBundle();
+
+ const suggestEl = document.querySelector('.js-suggest-gitlab-ci-yml');
+
+ if (suggestEl) {
+ initPopover(suggestEl);
+ }
+});
diff --git a/app/assets/javascripts/pages/projects/releases/show/index.js b/app/assets/javascripts/pages/projects/releases/show/index.js
new file mode 100644
index 00000000000..4e17e6ff311
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/releases/show/index.js
@@ -0,0 +1,3 @@
+import initShowRelease from '~/releases/mount_show';
+
+document.addEventListener('DOMContentLoaded', initShowRelease);
diff --git a/app/assets/javascripts/releases/components/app_edit.vue b/app/assets/javascripts/releases/components/app_edit.vue
index f6a4d00692e..6f4baaa5d74 100644
--- a/app/assets/javascripts/releases/components/app_edit.vue
+++ b/app/assets/javascripts/releases/components/app_edit.vue
@@ -1,10 +1,12 @@
<script>
import { mapState, mapActions } from 'vuex';
-import { GlButton, GlFormInput, GlFormGroup } from '@gitlab/ui';
+import { GlButton, GlLink, GlFormInput, GlFormGroup } from '@gitlab/ui';
import { escape as esc } from 'lodash';
import { __, sprintf } from '~/locale';
import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
+import { BACK_URL_PARAM } from '~/releases/constants';
+import { getParameterByName } from '~/lib/utils/common_utils';
export default {
name: 'ReleaseEditApp',
@@ -12,6 +14,7 @@ export default {
GlFormInput,
GlFormGroup,
GlButton,
+ GlLink,
MarkdownField,
},
directives: {
@@ -74,6 +77,9 @@ export default {
this.updateReleaseNotes(notes);
},
},
+ cancelPath() {
+ return getParameterByName(BACK_URL_PARAM) || this.releasesPagePath;
+ },
},
created() {
this.fetchRelease();
@@ -84,7 +90,6 @@ export default {
'updateRelease',
'updateReleaseTitle',
'updateReleaseNotes',
- 'navigateToReleasesPage',
]),
},
};
@@ -157,15 +162,9 @@ export default {
>
{{ __('Save changes') }}
</gl-button>
- <gl-button
- class="js-cancel-button"
- variant="default"
- type="button"
- :aria-label="__('Cancel')"
- @click="navigateToReleasesPage()"
- >
+ <gl-link :href="cancelPath" class="js-cancel-button btn btn-default">
{{ __('Cancel') }}
- </gl-button>
+ </gl-link>
</div>
</form>
</div>
diff --git a/app/assets/javascripts/releases/components/app_show.vue b/app/assets/javascripts/releases/components/app_show.vue
new file mode 100644
index 00000000000..d521edcc361
--- /dev/null
+++ b/app/assets/javascripts/releases/components/app_show.vue
@@ -0,0 +1,29 @@
+<script>
+import { mapState, mapActions } from 'vuex';
+import { GlSkeletonLoading } from '@gitlab/ui';
+import ReleaseBlock from './release_block.vue';
+
+export default {
+ name: 'ReleaseShowApp',
+ components: {
+ GlSkeletonLoading,
+ ReleaseBlock,
+ },
+ computed: {
+ ...mapState('detail', ['isFetchingRelease', 'fetchError', 'release']),
+ },
+ created() {
+ this.fetchRelease();
+ },
+ methods: {
+ ...mapActions('detail', ['fetchRelease']),
+ },
+};
+</script>
+<template>
+ <div class="prepend-top-default">
+ <gl-skeleton-loading v-if="isFetchingRelease" />
+
+ <release-block v-else-if="!fetchError" :release="release" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/releases/components/release_block_header.vue b/app/assets/javascripts/releases/components/release_block_header.vue
index 0bc2a5ce2eb..6f7e1dcfe2f 100644
--- a/app/assets/javascripts/releases/components/release_block_header.vue
+++ b/app/assets/javascripts/releases/components/release_block_header.vue
@@ -1,6 +1,8 @@
<script>
import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
+import { BACK_URL_PARAM } from '~/releases/constants';
+import { setUrlParams } from '~/lib/utils/url_utility';
export default {
name: 'ReleaseBlockHeader',
@@ -20,7 +22,15 @@ export default {
},
computed: {
editLink() {
- return this.release._links?.editUrl;
+ if (this.release._links?.editUrl) {
+ const queryParams = {
+ [BACK_URL_PARAM]: window.location.href,
+ };
+
+ return setUrlParams(queryParams, this.release._links.editUrl);
+ }
+
+ return undefined;
},
selfLink() {
return this.release._links?.self;
diff --git a/app/assets/javascripts/releases/constants.js b/app/assets/javascripts/releases/constants.js
index defcd917465..1db93323a87 100644
--- a/app/assets/javascripts/releases/constants.js
+++ b/app/assets/javascripts/releases/constants.js
@@ -1,7 +1,3 @@
-/* eslint-disable import/prefer-default-export */
-// This eslint-disable ^^^ can be removed when at least
-// one more constant is added to this file. Currently
-// constants.js files with only a single constant
-// are flagged by this rule.
-
export const MAX_MILESTONES_TO_DISPLAY = 5;
+
+export const BACK_URL_PARAM = 'back_url';
diff --git a/app/assets/javascripts/releases/mount_edit.js b/app/assets/javascripts/releases/mount_edit.js
index 2bc2728312a..102c4367aac 100644
--- a/app/assets/javascripts/releases/mount_edit.js
+++ b/app/assets/javascripts/releases/mount_edit.js
@@ -6,7 +6,15 @@ import detailModule from './stores/modules/detail';
export default () => {
const el = document.getElementById('js-edit-release-page');
- const store = createStore({ detail: detailModule });
+ const store = createStore({
+ modules: {
+ detail: detailModule,
+ },
+ featureFlags: {
+ releaseShowPage: Boolean(gon.features?.releaseShowPage),
+ },
+ });
+
store.dispatch('detail/setInitialState', el.dataset);
return new Vue({
diff --git a/app/assets/javascripts/releases/mount_index.js b/app/assets/javascripts/releases/mount_index.js
index 6fcb6d802e4..ad82d9a65d6 100644
--- a/app/assets/javascripts/releases/mount_index.js
+++ b/app/assets/javascripts/releases/mount_index.js
@@ -8,7 +8,11 @@ export default () => {
return new Vue({
el,
- store: createStore({ list: listModule }),
+ store: createStore({
+ modules: {
+ list: listModule,
+ },
+ }),
render: h =>
h(ReleaseListApp, {
props: {
diff --git a/app/assets/javascripts/releases/mount_show.js b/app/assets/javascripts/releases/mount_show.js
new file mode 100644
index 00000000000..73e34869b21
--- /dev/null
+++ b/app/assets/javascripts/releases/mount_show.js
@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import ReleaseShowApp from './components/app_show.vue';
+import createStore from './stores';
+import detailModule from './stores/modules/detail';
+
+export default () => {
+ const el = document.getElementById('js-show-release-page');
+
+ const store = createStore({
+ modules: {
+ detail: detailModule,
+ },
+ });
+ store.dispatch('detail/setInitialState', el.dataset);
+
+ return new Vue({
+ el,
+ store,
+ render: h => h(ReleaseShowApp),
+ });
+};
diff --git a/app/assets/javascripts/releases/stores/index.js b/app/assets/javascripts/releases/stores/index.js
index aa607906a0e..7f211145ccf 100644
--- a/app/assets/javascripts/releases/stores/index.js
+++ b/app/assets/javascripts/releases/stores/index.js
@@ -3,4 +3,8 @@ import Vuex from 'vuex';
Vue.use(Vuex);
-export default modules => new Vuex.Store({ modules });
+export default ({ modules, featureFlags }) =>
+ new Vuex.Store({
+ modules,
+ state: { featureFlags },
+ });
diff --git a/app/assets/javascripts/releases/stores/modules/detail/actions.js b/app/assets/javascripts/releases/stores/modules/detail/actions.js
index f730af1c7dc..35901a654b0 100644
--- a/app/assets/javascripts/releases/stores/modules/detail/actions.js
+++ b/app/assets/javascripts/releases/stores/modules/detail/actions.js
@@ -33,9 +33,11 @@ export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_REL
export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes);
export const requestUpdateRelease = ({ commit }) => commit(types.REQUEST_UPDATE_RELEASE);
-export const receiveUpdateReleaseSuccess = ({ commit, dispatch }) => {
+export const receiveUpdateReleaseSuccess = ({ commit, state, rootState }) => {
commit(types.RECEIVE_UPDATE_RELEASE_SUCCESS);
- dispatch('navigateToReleasesPage');
+ redirectTo(
+ rootState.featureFlags.releaseShowPage ? state.release._links.self : state.releasesPagePath,
+ );
};
export const receiveUpdateReleaseError = ({ commit }, error) => {
commit(types.RECEIVE_UPDATE_RELEASE_ERROR, error);
diff --git a/app/assets/stylesheets/components/popover.scss b/app/assets/stylesheets/components/popover.scss
index 6654553aaa2..7e824c72f77 100644
--- a/app/assets/stylesheets/components/popover.scss
+++ b/app/assets/stylesheets/components/popover.scss
@@ -138,3 +138,15 @@
max-width: 40%;
}
}
+
+.suggest-gitlab-ci-yml {
+ margin-top: -1em;
+
+ .popover-header {
+ padding: $gl-padding;
+
+ .ic-close {
+ margin-top: -1em;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index e4853ca7bf5..d31d9245e9c 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -81,6 +81,7 @@ $gl-gray-400: #999;
$gl-gray-500: #777;
$gl-gray-600: #666;
$gl-gray-700: #555;
+$gl-gray-800: #333;
$green-50: #f1fdf6;
$green-100: #dcf5e7;
diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss
index d77b7dfad68..ea82ba3e879 100644
--- a/app/assets/stylesheets/notify.scss
+++ b/app/assets/stylesheets/notify.scss
@@ -18,3 +18,19 @@ p.details {
pre.commit-message {
white-space: pre-wrap;
}
+
+.gl-label-scoped {
+ box-shadow: 0 0 0 2px currentColor inset;
+}
+
+.gl-label-text {
+ padding: 0 5px;
+}
+
+.gl-label-text-light {
+ color: $white-light;
+}
+
+.gl-label-text-dark {
+ color: $gl-gray-800;
+}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index f8832047d49..8b2c67378d9 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -1006,6 +1006,14 @@ pre.light-well {
}
}
+ &:not(.with-pipeline-status) {
+ .icon-wrapper:first-of-type {
+ @include media-breakpoint-up(lg) {
+ margin-left: $gl-padding-32;
+ }
+ }
+ }
+
.ci-status-link {
display: inline-flex;
}
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index a0228c6bd94..4f66356c27e 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -79,7 +79,7 @@ module MarkupHelper
md = markdown_field(object, attribute, options.merge(post_process: false))
return unless md.present?
- tags = %w(a gl-emoji b pre code p span)
+ tags = %w(a gl-emoji b strong i em pre code p span)
tags << 'img' if options[:allow_images]
text = truncate_visible(md, max_chars || md.length)
@@ -88,7 +88,7 @@ module MarkupHelper
text,
tags: tags,
attributes: Rails::Html::WhiteListSanitizer.allowed_attributes +
- %w(style data-src data-name data-unicode-version data-iid data-project-path data-mr-title)
+ %w(style data-src data-name data-unicode-version data-iid data-project-path data-mr-title data-html)
)
# since <img> tags are stripped, this can leave empty <a> tags hanging around
@@ -233,7 +233,7 @@ module MarkupHelper
def strip_empty_link_tags(text)
scrubber = Loofah::Scrubber.new do |node|
- node.remove if node.name == 'a' && node.content.blank?
+ node.remove if node.name == 'a' && node.children.empty?
end
sanitize text, scrubber: scrubber
diff --git a/app/helpers/suggest_pipeline_helper.rb b/app/helpers/suggest_pipeline_helper.rb
new file mode 100644
index 00000000000..aa67f0ea770
--- /dev/null
+++ b/app/helpers/suggest_pipeline_helper.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module SuggestPipelineHelper
+ def should_suggest_gitlab_ci_yml?
+ Feature.enabled?(:suggest_pipeline) &&
+ current_user &&
+ params[:suggest_gitlab_ci_yml] == 'true'
+ end
+end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index f265b72f11f..d3f597c0bda 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -305,6 +305,10 @@ class Issue < ApplicationRecord
labels.map(&:hook_attrs)
end
+ def previous_updated_at
+ previous_changes['updated_at']&.first || updated_at
+ end
+
private
def ensure_metrics
diff --git a/app/models/milestone_note.rb b/app/models/milestone_note.rb
index 8ff0503502f..4b027b0782c 100644
--- a/app/models/milestone_note.rb
+++ b/app/models/milestone_note.rb
@@ -12,6 +12,7 @@ class MilestoneNote < ::Note
created_at: event.created_at,
noteable: resource,
milestone: event.milestone,
+ discussion_id: event.discussion_id,
event: event,
system_note_metadata: ::SystemNoteMetadata.new(action: 'milestone'),
resource_parent: resource_parent
diff --git a/app/models/resource_event.rb b/app/models/resource_event.rb
index 2c0052b0be3..86e11c2d568 100644
--- a/app/models/resource_event.rb
+++ b/app/models/resource_event.rb
@@ -21,7 +21,7 @@ class ResourceEvent < ApplicationRecord
private
def discussion_id_key
- [self.class.name, created_at, user_id]
+ [self.class.name, id, user_id]
end
def exactly_one_issuable
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index 970d4e1e562..8e66310f0c5 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -103,6 +103,10 @@ class ResourceLabelEvent < ResourceEvent
def resource_parent
issuable.project || issuable.group
end
+
+ def discussion_id_key
+ [self.class.name, created_at, user_id]
+ end
end
ResourceLabelEvent.prepend_if_ee('EE::ResourceLabelEvent')
diff --git a/app/views/dashboard/projects/_projects.html.haml b/app/views/dashboard/projects/_projects.html.haml
index ca201e626b8..5122164dbcb 100644
--- a/app/views/dashboard/projects/_projects.html.haml
+++ b/app/views/dashboard/projects/_projects.html.haml
@@ -1 +1 @@
-= render 'shared/projects/list', projects: @projects, user: current_user
+= render 'shared/projects/list', projects: @projects, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true), user: current_user
diff --git a/app/views/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 35b32662b8a..d819c4ea554 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1,2 +1,2 @@
- is_explore_page = defined?(explore_page) && explore_page
-= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page
+= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page, pipeline_status: Feature.enabled?(:dashboard_pipeline_status, default_enabled: true)
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 961b873b571..738bca111cd 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -17,8 +17,10 @@
%span.pull-left.append-right-10
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
- required: true, class: 'form-control new-file-name js-file-path-name-input'
+ required: true, class: 'form-control new-file-name js-file-path-name-input', value: params[:file_name] || (should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : '')
= render 'template_selectors'
+ - if should_suggest_gitlab_ci_yml?
+ = render partial: 'suggest_gitlab_ci_yml', locals: { target: '#gitlab-ci-yml-selector', dismiss_key: "suggest_gitlab_ci_yml_#{@project.id}" }
.file-buttons
- if is_markdown
diff --git a/app/views/projects/blob/_suggest_gitlab_ci_yml.html.haml b/app/views/projects/blob/_suggest_gitlab_ci_yml.html.haml
new file mode 100644
index 00000000000..6b368033c1e
--- /dev/null
+++ b/app/views/projects/blob/_suggest_gitlab_ci_yml.html.haml
@@ -0,0 +1,4 @@
+.js-suggest-gitlab-ci-yml{ data: { toggle: 'popover',
+ target: target,
+ css_class: 'suggest-gitlab-ci-yml ml-4',
+ dismiss_key: dismiss_key } }
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
index 5ecfa135446..2be95bc5541 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -1,12 +1,13 @@
.template-selectors-menu.gl-pl-2
.template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden
- = dropdown_tag(_("Select a template type"), options: { toggle_class: 'js-template-type-selector qa-template-type-dropdown', dropdown_class: 'dropdown-menu-selectable'} )
+ - toggle_text = should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : 'Select a template type'
+ = dropdown_tag(_(toggle_text), options: { toggle_class: 'js-template-type-selector qa-template-type-dropdown', dropdown_class: 'dropdown-menu-selectable' })
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-license-selector qa-license-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: licenses_for_select(@project), project: @project.name, fullname: @project.namespace.human_name } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: gitignore_names(@project) } } )
- .gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
+ #gitlab-ci-yml-selector.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-gitlab-ci-yml-selector qa-gitlab-ci-yml-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls(@project) } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
= dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-dockerfile-selector qa-dockerfile-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: dockerfile_names(@project) } } )
diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml
index 55c702b967f..92680a70da2 100644
--- a/app/views/projects/runners/_runner.html.haml
+++ b/app/views/projects/runners/_runner.html.haml
@@ -3,7 +3,7 @@
= runner_status_icon(runner)
- if @project_runners.include?(runner)
- = link_to runner.short_sha.concat("..."), project_runner_path(@project, runner), class: 'commit-sha has-tooltip', title: _("Partial token for reference only")
+ = link_to _("%{token}...") % { token: runner.short_sha }, project_runner_path(@project, runner), class: 'commit-sha has-tooltip', title: _("Partial token for reference only")
- if runner.locked?
= icon('lock', class: 'has-tooltip', title: _('Locked to current projects'))
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 144bb04e2a8..d29ba3eedc6 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,7 +12,9 @@
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
+- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
- css_controls_class = compact_mode ? [] : ["flex-lg-row", "justify-content-lg-between"]
+- css_controls_class << "with-pipeline-status" if show_pipeline_status_icon
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
- license_name = project_license_name(project)
@@ -61,6 +63,11 @@
.controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") }
.icon-container.d-flex.align-items-center
+ - if show_pipeline_status_icon
+ - pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
+ %span.icon-wrapper.pipeline-status
+ = render 'ci/status/icon', status: project.last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
+
= render_if_exists 'shared/projects/archived', project: project
- if stars
= link_to project_starrers_path(project),