diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-04 09:08:20 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-04 09:08:20 +0000 |
commit | d80f3cd75e700b6e62910865bfd36734644ffa89 (patch) | |
tree | aa2fa2f2b4385854c13591bef8e74924ef661657 /app | |
parent | be81c1578d65f25edfde8aa550f190b8d3e6d976 (diff) | |
download | gitlab-ce-d80f3cd75e700b6e62910865bfd36734644ffa89.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
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), |