diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-25 21:08:15 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-05-25 21:08:15 +0300 |
commit | 28b119a4b47d3a41c4879aab651221b85289bc69 (patch) | |
tree | 5482e008b585e7170a54f7e67e0e62bdb091b7f5 /app | |
parent | 4dc41ac252c0bfefb9bc55a8627262cc76c69d5e (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
45 files changed, 390 insertions, 408 deletions
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js index 5119d5021da..60c3f3caf66 100644 --- a/app/assets/javascripts/behaviors/markdown/render_gfm.js +++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js @@ -1,6 +1,5 @@ import $ from 'jquery'; import syntaxHighlight from '~/syntax_highlight'; -import initUserPopovers from '../../user_popovers'; import highlightCurrentUser from './highlight_current_user'; import { renderKroki } from './render_kroki'; import renderMath from './render_math'; @@ -22,7 +21,6 @@ $.fn.renderGFM = function renderGFM() { renderMermaid(this.find('.js-render-mermaid')); } highlightCurrentUser(this.find('.gfm-project_member').get()); - initUserPopovers(this.find('.js-user-link').get()); const issuablePopoverElements = this.find('.gfm-issue, .gfm-merge_request').get(); if (issuablePopoverElements.length) { diff --git a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue index dca89133931..8a997624a36 100644 --- a/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue +++ b/app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue @@ -2,29 +2,9 @@ import { GlModal, GlButton, GlFormInput, GlSprintf } from '@gitlab/ui'; import csrf from '~/lib/utils/csrf'; import { s__ } from '~/locale'; -import SplitButton from '~/vue_shared/components/split_button.vue'; - -const splitButtonActionItems = [ - { - title: s__('ClusterIntegration|Remove integration and resources'), - description: s__( - 'ClusterIntegration|Deletes all GitLab resources attached to this cluster during removal', - ), - eventName: 'remove-cluster-and-cleanup', - }, - { - title: s__('ClusterIntegration|Remove integration'), - description: s__( - 'ClusterIntegration|Removes cluster from project but keeps associated resources', - ), - eventName: 'remove-cluster', - }, -]; export default { - splitButtonActionItems, components: { - SplitButton, GlModal, GlButton, GlFormInput, @@ -79,6 +59,9 @@ export default { canCleanupResources() { return !this.hasManagementProject; }, + buttonCategory() { + return !this.hasManagementProject ? 'secondary' : 'primary'; + }, }, methods: { handleClickRemoveCluster(cleanup = false) { @@ -99,19 +82,20 @@ export default { </script> <template> - <div class="gl-display-flex gl-justify-content-end"> - <split-button + <div class="gl-display-flex"> + <gl-button v-if="canCleanupResources" - :action-items="$options.splitButtonActionItems" - menu-class="dropdown-menu-large" + data-testid="remove-integration-and-resources-button" + class="gl-mr-3" variant="danger" - @remove-cluster="handleClickRemoveCluster(false)" - @remove-cluster-and-cleanup="handleClickRemoveCluster(true)" - /> + @click="handleClickRemoveCluster(true)" + > + {{ s__('ClusterIntegration|Remove integration and resources') }} + </gl-button> <gl-button - v-else + data-testid="remove-integration-button" + :category="buttonCategory" variant="danger" - data-testid="btnRemove" @click="handleClickRemoveCluster(false)" > {{ s__('ClusterIntegration|Remove integration') }} @@ -163,13 +147,7 @@ export default { <template v-if="confirmCleanup"> <gl-button :disabled="!canSubmit" - variant="warning" - category="primary" - @click="handleSubmit" - >{{ s__('ClusterIntegration|Remove integration') }}</gl-button - > - <gl-button - :disabled="!canSubmit" + data-testid="remove-integration-and-resources-modal-button" variant="danger" category="primary" @click="handleSubmit(true)" @@ -179,6 +157,7 @@ export default { <template v-else> <gl-button :disabled="!canSubmit" + data-testid="remove-integration-modal-button" variant="danger" category="primary" @click="handleSubmit" diff --git a/app/assets/javascripts/content_editor/extensions/sourcemap.js b/app/assets/javascripts/content_editor/extensions/sourcemap.js index 94236e2e70e..61d1d983846 100644 --- a/app/assets/javascripts/content_editor/extensions/sourcemap.js +++ b/app/assets/javascripts/content_editor/extensions/sourcemap.js @@ -13,6 +13,7 @@ import Link from './link'; import ListItem from './list_item'; import OrderedList from './ordered_list'; import Paragraph from './paragraph'; +import Strike from './strike'; export default Extension.create({ addGlobalAttributes() { @@ -33,6 +34,7 @@ export default Extension.create({ ListItem.name, OrderedList.name, Paragraph.name, + Strike.name, ], attributes: { sourceMarkdown: { diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index d665f24bba1..cd3ac6fcb1f 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -65,6 +65,7 @@ import { italic, link, code, + strike, } from './serialization_helpers'; const defaultSerializerConfig = { @@ -89,12 +90,7 @@ const defaultSerializerConfig = { close: (...args) => `${defaultMarkdownSerializer.marks.code.close(...args)}$`, escape: false, }, - [Strike.name]: { - open: '~~', - close: '~~', - mixable: true, - expelEnclosingWhitespace: true, - }, + [Strike.name]: strike, ...HTMLMarks.reduce( (acc, { name }) => ({ ...acc, diff --git a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js index 899bbcff82f..4cbbfc36151 100644 --- a/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js @@ -66,6 +66,10 @@ const factorySpecs = { title: hastNode.properties.title, }), }, + strike: { + type: 'mark', + selector: (hastNode) => ['strike', 's', 'del'].includes(hastNode.tagName), + }, }; export default () => { diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index 089d30edec7..c11ce08de63 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -364,7 +364,7 @@ export function preserveUnchanged(render) { }; } -const generateBoldTags = (open = true) => { +const generateBoldTags = (wrapTagName = openTag) => { return (_, mark) => { const type = /^(\*\*|__|<strong|<b).*/.exec(mark.attrs.sourceMarkdown)?.[1]; @@ -375,7 +375,7 @@ const generateBoldTags = (open = true) => { // eslint-disable-next-line @gitlab/require-i18n-strings case '<strong': case '<b': - return (open ? openTag : closeTag)(type.substring(1)); + return wrapTagName(type.substring(1)); default: return '**'; } @@ -384,12 +384,12 @@ const generateBoldTags = (open = true) => { export const bold = { open: generateBoldTags(), - close: generateBoldTags(false), + close: generateBoldTags(closeTag), mixable: true, expelEnclosingWhitespace: true, }; -const generateItalicTag = (open = true) => { +const generateItalicTag = (wrapTagName = openTag) => { return (_, mark) => { const type = /^(\*|_|<em|<i).*/.exec(mark.attrs.sourceMarkdown)?.[1]; @@ -400,7 +400,7 @@ const generateItalicTag = (open = true) => { // eslint-disable-next-line @gitlab/require-i18n-strings case '<em': case '<i': - return (open ? openTag : closeTag)(type.substring(1)); + return wrapTagName(type.substring(1)); default: return '_'; } @@ -409,17 +409,17 @@ const generateItalicTag = (open = true) => { export const italic = { open: generateItalicTag(), - close: generateItalicTag(false), + close: generateItalicTag(closeTag), mixable: true, expelEnclosingWhitespace: true, }; -const generateCodeTag = (open = true) => { +const generateCodeTag = (wrapTagName = openTag) => { return (_, mark) => { const type = /^(`|<code).*/.exec(mark.attrs.sourceMarkdown)?.[1]; if (type === '<code') { - return (open ? openTag : closeTag)(type.substring(1)); + return wrapTagName(type.substring(1)); } return '`'; @@ -428,7 +428,7 @@ const generateCodeTag = (open = true) => { export const code = { open: generateCodeTag(), - close: generateCodeTag(false), + close: generateCodeTag(closeTag), mixable: true, expelEnclosingWhitespace: true, }; @@ -480,3 +480,28 @@ export const link = { return `](${state.esc(canonicalSrc || href)}${title ? ` ${state.quote(title)}` : ''})`; }, }; + +const generateStrikeTag = (wrapTagName = openTag) => { + return (_, mark) => { + const type = /^(~~|<del|<strike|<s).*/.exec(mark.attrs.sourceMarkdown)?.[1]; + + switch (type) { + case '~~': + return type; + /* eslint-disable @gitlab/require-i18n-strings */ + case '<del': + case '<strike': + case '<s': + return wrapTagName(type.substring(1)); + default: + return '~~'; + } + }; +}; + +export const strike = { + open: generateStrikeTag(), + close: generateStrikeTag(closeTag), + mixable: true, + expelEnclosingWhitespace: true, +}; diff --git a/app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue b/app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue index 3158ae9b126..ccd22085470 100644 --- a/app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue +++ b/app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue @@ -22,7 +22,7 @@ export default { type: Boolean, required: true, }, - editProjectServicePath: { + editIntegrationPath: { type: String, required: true, }, @@ -79,7 +79,7 @@ export default { <gl-button variant="success" category="primary" :disabled="!formIsValid" @click="submit"> {{ saveButtonText }} </gl-button> - <gl-button class="float-right" :href="editProjectServicePath">{{ __('Cancel') }}</gl-button> + <gl-button class="float-right" :href="editIntegrationPath">{{ __('Cancel') }}</gl-button> <delete-custom-metric-modal v-if="metricPersisted" :delete-metric-url="customMetricsPath" diff --git a/app/assets/javascripts/custom_metrics/index.js b/app/assets/javascripts/custom_metrics/index.js index 4c279daf5f0..bf572217f5e 100644 --- a/app/assets/javascripts/custom_metrics/index.js +++ b/app/assets/javascripts/custom_metrics/index.js @@ -13,7 +13,7 @@ export default () => { const domEl = document.querySelector(this.$options.el); const { customMetricsPath, - editProjectServicePath, + editIntegrationPath, validateQueryPath, title, query, @@ -30,7 +30,7 @@ export default () => { props: { customMetricsPath, metricPersisted, - editProjectServicePath, + editIntegrationPath, validateQueryPath, formData: { title, diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 42f4ea8eb58..fc69dca73a7 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -7,8 +7,6 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import initUserPopovers from '../../user_popovers'; - /** * CommitItem * @@ -82,11 +80,6 @@ export default { return this.commit.description_html.replace(/^
/, ''); }, }, - created() { - this.$nextTick(() => { - initUserPopovers(this.$el.querySelectorAll('.js-user-link')); - }); - }, safeHtmlConfig: { ADD_TAGS: ['gl-emoji'], }, diff --git a/app/assets/javascripts/lib/gfm/index.js b/app/assets/javascripts/lib/gfm/index.js index 4e704eb69b2..537a5867096 100644 --- a/app/assets/javascripts/lib/gfm/index.js +++ b/app/assets/javascripts/lib/gfm/index.js @@ -1,10 +1,15 @@ import { unified } from 'unified'; import remarkParse from 'remark-parse'; +import remarkGfm from 'remark-gfm'; import remarkRehype from 'remark-rehype'; import rehypeRaw from 'rehype-raw'; const createParser = () => { - return unified().use(remarkParse).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw); + return unified() + .use(remarkParse) + .use(remarkGfm) + .use(remarkRehype, { allowDangerousHtml: true }) + .use(rehypeRaw); }; const compilerFactory = (renderer) => diff --git a/app/assets/javascripts/lib/utils/users_cache.js b/app/assets/javascripts/lib/utils/users_cache.js index bd000bb26fe..670acbbabd7 100644 --- a/app/assets/javascripts/lib/utils/users_cache.js +++ b/app/assets/javascripts/lib/utils/users_cache.js @@ -29,8 +29,11 @@ class UsersCache extends Cache { } return getUser(userId).then(({ data }) => { - this.internalStorage[userId] = data; - return data; + this.internalStorage[userId] = { + ...this.get(userId), + ...data, + }; + return this.internalStorage[userId]; }); // missing catch is intentional, error handling depends on use case } diff --git a/app/assets/javascripts/members/components/table/members_table.vue b/app/assets/javascripts/members/components/table/members_table.vue index 14d628e455c..4f99e7a2aef 100644 --- a/app/assets/javascripts/members/components/table/members_table.vue +++ b/app/assets/javascripts/members/components/table/members_table.vue @@ -4,7 +4,6 @@ import { mapState } from 'vuex'; import MembersTableCell from 'ee_else_ce/members/components/table/members_table_cell.vue'; import { canOverride, canRemove, canResend, canUpdate } from 'ee_else_ce/members/utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; -import initUserPopovers from '~/user_popovers'; import UserDate from '~/vue_shared/components/user_date.vue'; import { FIELD_KEY_ACTIONS, @@ -85,9 +84,6 @@ export default { return this.tabQueryParamValue === TAB_QUERY_PARAM_VALUES.invite; }, }, - mounted() { - initUserPopovers(this.$el.querySelectorAll('.js-user-link')); - }, methods: { hasActionButtons(member) { return ( diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue index 7d8d23335e0..148a73100ee 100644 --- a/app/assets/javascripts/notes/components/notes_app.vue +++ b/app/assets/javascripts/notes/components/notes_app.vue @@ -3,7 +3,6 @@ import { mapGetters, mapActions } from 'vuex'; import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; import createFlash from '~/flash'; import { __ } from '~/locale'; -import initUserPopovers from '~/user_popovers'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; import OrderedLayout from '~/vue_shared/components/ordered_layout.vue'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -169,7 +168,6 @@ export default { updated() { this.$nextTick(() => { highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member')); - initUserPopovers(this.$el.querySelectorAll('.js-user-link')); }); }, beforeDestroy() { diff --git a/app/assets/javascripts/pages/projects/services/edit/index.js b/app/assets/javascripts/pages/projects/settings/integrations/edit/index.js index 64df0d07d74..64df0d07d74 100644 --- a/app/assets/javascripts/pages/projects/services/edit/index.js +++ b/app/assets/javascripts/pages/projects/settings/integrations/edit/index.js diff --git a/app/assets/javascripts/pages/projects/settings/integrations/show/index.js b/app/assets/javascripts/pages/projects/settings/integrations/index/index.js index 53068f72d3f..53068f72d3f 100644 --- a/app/assets/javascripts/pages/projects/settings/integrations/show/index.js +++ b/app/assets/javascripts/pages/projects/settings/integrations/index/index.js diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue index e35fccf2d7e..05cb2ebb769 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue @@ -36,12 +36,12 @@ export default { }; </script> <template> - <div data-testid="pipeline-mini-graph" class="gl-display-inline gl-vertical-align-middle gl-my-1"> + <div data-testid="pipeline-mini-graph" class="gl-display-inline gl-vertical-align-middle"> <div v-for="stage in stages" :key="stage.name" :class="stagesClass" - class="stage-container dropdown" + class="dropdown gl-display-inline-block gl-mr-2 gl-my-2 gl-vertical-align-middle stage-container" > <pipeline-stage :stage="stage" diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue index 53e21d4ce8b..008b5780ecd 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue @@ -21,6 +21,9 @@ import eventHub from '../../event_hub'; import JobItem from './job_item.vue'; export default { + i18n: { + stage: __('Stage:'), + }, components: { CiIcon, GlLoadingIcon, @@ -48,20 +51,26 @@ export default { }, data() { return { + isDropdownOpen: false, isLoading: false, dropdownContent: [], + stageName: '', }; }, watch: { updateDropdown() { - if (this.updateDropdown && this.isDropdownOpen() && !this.isLoading) { + if (this.updateDropdown && this.isDropdownOpen && !this.isLoading) { this.fetchJobs(); } }, }, methods: { + onHideDropdown() { + this.isDropdownOpen = false; + }, onShowDropdown() { eventHub.$emit('clickedDropdown'); + this.isDropdownOpen = true; this.isLoading = true; this.fetchJobs(); }, @@ -70,6 +79,7 @@ export default { .get(this.stage.dropdown_path) .then(({ data }) => { this.dropdownContent = data.latest_statuses; + this.stageName = data.name; this.isLoading = false; }) .catch(() => { @@ -81,9 +91,6 @@ export default { }); }); }, - isDropdownOpen() { - return this.$el.classList.contains('show'); - }, pipelineActionRequestComplete() { // close the dropdown in MR widget this.$refs.dropdown.hide(); @@ -112,15 +119,17 @@ export default { } /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" :toggle-class="['gl-rounded-full!']" menu-class="mini-pipeline-graph-dropdown-menu" + @hide="onHideDropdown" @show="onShowDropdown" > <template #button-content> <ci-icon is-interactive css-classes="gl-rounded-full" + :is-active="isDropdownOpen" :size="24" :status="stage.status" - class="gl-align-items-center gl-display-inline-flex" + class="gl-align-items-center gl-display-inline-flex gl-z-index-1" /> </template> <gl-loading-icon v-if="isLoading" size="sm" /> @@ -129,6 +138,12 @@ export default { class="js-builds-dropdown-list scrollable-menu" data-testid="mini-pipeline-graph-dropdown-menu-list" > + <div + class="gl-align-items-center gl-border-b gl-display-flex gl-font-weight-bold gl-justify-content-center gl-pb-3" + > + <span class="gl-mr-1">{{ $options.i18n.stage }}</span> + <span data-testid="pipeline-stage-dropdown-menu-title">{{ stageName }}</span> + </div> <li v-for="job in dropdownContent" :key="job.id"> <job-item :dropdown-length="dropdownContent.length" diff --git a/app/assets/javascripts/user_popovers.js b/app/assets/javascripts/user_popovers.js index 438ae2bc1bc..a3615eab26f 100644 --- a/app/assets/javascripts/user_popovers.js +++ b/app/assets/javascripts/user_popovers.js @@ -1,6 +1,8 @@ import Vue from 'vue'; +import { debounce } from 'lodash'; import UsersCache from './lib/utils/users_cache'; import UserPopover from './vue_shared/components/user_popover/user_popover.vue'; +import { USER_POPOVER_DELAY } from './vue_shared/components/user_popover/constants'; const removeTitle = (el) => { // Removing titles so its not showing tooltips also @@ -59,87 +61,78 @@ const populateUserInfo = (user) => { ); }; -const initializedPopovers = new Map(); -let domObservedForChanges = false; +function createPopover(el, user) { + removeTitle(el); + const preloadedUserInfo = getPreloadedUserInfo(el.dataset); -const addPopoversToModifiedTree = new MutationObserver(() => { - const userLinks = document?.querySelectorAll('.js-user-link, .gfm-project_member'); + Object.assign(user, preloadedUserInfo); - if (userLinks) { - addPopovers(userLinks); /* eslint-disable-line no-use-before-define */ + if (preloadedUserInfo.userId) { + populateUserInfo(user); } -}); + const UserPopoverComponent = Vue.extend(UserPopover); + return new UserPopoverComponent({ + propsData: { + target: el, + user, + show: true, + placement: el.dataset.placement || 'top', + }, + }); +} -function observeBody() { - if (!domObservedForChanges) { - addPopoversToModifiedTree.observe(document.body, { - subtree: true, - childList: true, - }); +function launchPopover(el, mountPopover) { + if (el.user) return; - domObservedForChanges = true; - } + const emptyUser = { + location: null, + bio: null, + workInformation: null, + status: null, + isFollowed: false, + loaded: false, + }; + el.user = emptyUser; + el.addEventListener( + 'mouseleave', + ({ target }) => { + target.removeAttribute('aria-describedby'); + }, + { once: true }, + ); + const popoverInstance = createPopover(el, emptyUser); + + const { userId } = el.dataset; + + popoverInstance.$on('follow', () => { + UsersCache.updateById(userId, { is_followed: true }); + el.user.isFollowed = true; + }); + + popoverInstance.$on('unfollow', () => { + UsersCache.updateById(userId, { is_followed: false }); + el.user.isFollowed = false; + }); + + mountPopover(popoverInstance); } -export default function addPopovers(elements = document.querySelectorAll('.js-user-link')) { - const userLinks = Array.from(elements); - const UserPopoverComponent = Vue.extend(UserPopover); +const userLinkSelector = 'a.js-user-link, a.gfm-project_member'; - observeBody(); +const getUserLinkNode = (node) => node.closest(userLinkSelector); - return userLinks - .filter(({ dataset }) => dataset.user || dataset.userId) - .map((el) => { - if (initializedPopovers.has(el)) { - return initializedPopovers.get(el); - } +const lazyLaunchPopover = debounce((mountPopover, event) => { + const userLink = getUserLinkNode(event.target); + if (userLink) { + launchPopover(userLink, mountPopover); + } +}, USER_POPOVER_DELAY); - const user = { - location: null, - bio: null, - workInformation: null, - status: null, - isFollowed: false, - loaded: false, - }; - const renderedPopover = new UserPopoverComponent({ - propsData: { - target: el, - user, - placement: el.dataset.placement || 'top', - }, - }); - - const { userId } = el.dataset; - - renderedPopover.$on('follow', () => { - UsersCache.updateById(userId, { is_followed: true }); - user.isFollowed = true; - }); - - renderedPopover.$on('unfollow', () => { - UsersCache.updateById(userId, { is_followed: false }); - user.isFollowed = false; - }); - - initializedPopovers.set(el, renderedPopover); - - renderedPopover.$mount(); - - el.addEventListener('mouseenter', ({ target }) => { - removeTitle(target); - const preloadedUserInfo = getPreloadedUserInfo(target.dataset); - - Object.assign(user, preloadedUserInfo); - - if (preloadedUserInfo.userId) { - populateUserInfo(user); - } - }); - el.addEventListener('mouseleave', ({ target }) => { - target.removeAttribute('aria-describedby'); - }); - - return renderedPopover; - }); +let hasAddedLazyPopovers = false; + +export default function addPopovers(mountPopover = (instance) => instance.$mount()) { + if (!hasAddedLazyPopovers) { + document.addEventListener('mouseover', (event) => lazyLaunchPopover(mountPopover, event)); + hasAddedLazyPopovers = true; + } } diff --git a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue index c93f620995f..afa5402c28c 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue @@ -18,7 +18,6 @@ import { toggleContainerClasses } from '~/lib/utils/dom_utils'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; import { s__ } from '~/locale'; import Tracking from '~/tracking'; -import initUserPopovers from '~/user_popovers'; import AlertDetailsTable from '~/vue_shared/components/alert_details_table.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; import MetricImagesTab from '~/vue_shared/components/metric_images/metric_images_tab.vue'; @@ -175,7 +174,6 @@ export default { updated() { this.$nextTick(() => { highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member')); - initUserPopovers(this.$el.querySelectorAll('.js-user-link')); }); }, methods: { diff --git a/app/assets/javascripts/vue_shared/components/ci_icon.vue b/app/assets/javascripts/vue_shared/components/ci_icon.vue index 9bccc49e894..c70f028a876 100644 --- a/app/assets/javascripts/vue_shared/components/ci_icon.vue +++ b/app/assets/javascripts/vue_shared/components/ci_icon.vue @@ -50,6 +50,11 @@ export default { required: false, default: false, }, + isActive: { + type: Boolean, + required: false, + default: false, + }, isInteractive: { type: Boolean, required: false, @@ -74,8 +79,9 @@ export default { </script> <template> <span - :class="[wrapperStyleClasses, { interactive: isInteractive }]" + :class="[wrapperStyleClasses, { interactive: isInteractive, active: isActive }]" :style="{ height: `${size}px`, width: `${size}px` }" + data-testid="ci-icon-wrapper" > <gl-icon :name="icon" :size="size" :class="cssClasses" :aria-label="status.icon" /> </span> diff --git a/app/assets/javascripts/vue_shared/components/user_popover/constants.js b/app/assets/javascripts/vue_shared/components/user_popover/constants.js new file mode 100644 index 00000000000..1d49aefd297 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/user_popover/constants.js @@ -0,0 +1 @@ +export const USER_POPOVER_DELAY = 200; diff --git a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue index ec7a7cd72ae..2eec65457c7 100644 --- a/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue +++ b/app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue @@ -14,12 +14,14 @@ import { glEmojiTag } from '~/emoji'; import createFlash from '~/flash'; import { followUser, unfollowUser } from '~/rest_api'; import UserAvatarImage from '../user_avatar/user_avatar_image.vue'; +import { USER_POPOVER_DELAY } from './constants'; const MAX_SKELETON_LINES = 4; export default { name: 'UserPopover', maxSkeletonLines: MAX_SKELETON_LINES, + USER_POPOVER_DELAY, components: { GlIcon, GlLink, @@ -48,6 +50,11 @@ export default { required: false, default: 'top', }, + show: { + type: Boolean, + required: false, + default: false, + }, }, data() { return { @@ -133,8 +140,15 @@ export default { </script> <template> - <!-- 200ms delay so not every mouseover triggers Popover --> - <gl-popover :target="target" :delay="200" :placement="placement" boundary="viewport"> + <!-- delay so not every mouseover triggers Popover --> + <gl-popover + :show="show" + :target="target" + :delay="$options.USER_POPOVER_DELAY" + :placement="placement" + boundary="viewport" + triggers="hover focus manual" + > <div class="gl-p-3 gl-line-height-normal gl-display-flex" data-testid="user-popover"> <div class="gl-p-2 flex-shrink-1 gl-display-flex gl-flex-direction-column align-items-center gl-w-70p" diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index ca0240b6a65..1a6a461dad8 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -1,94 +1,55 @@ -.ci-status-icon-success, -.ci-status-icon-passed { - svg { - fill: $green-500; +@mixin icon-styles($primary-color, $svg-color) { + svg, + .gl-icon { + fill: $primary-color; } &.interactive { &:hover { - background: $green-500; + background: $primary-color; - svg { - --svg-status-bg: #{$green-100}; - box-shadow: 0 0 0 1px $green-500; + .gl-icon { + --svg-status-bg: #{$svg-color}; + box-shadow: 0 0 0 1px $primary-color; } } - } -} -.ci-status-icon-error, -.ci-status-icon-failed { - svg { - fill: $red-500; - } + &.active { + background: $primary-color; - &.interactive { - &:hover { - background: $red-500; - - svg { - --svg-status-bg: #{$red-100}; - box-shadow: 0 0 0 1px $red-500; + .gl-icon { + box-shadow: 0 0 0 1px $primary-color; } } } } +.ci-status-icon-success, +.ci-status-icon-passed { + @include icon-styles($green-500, $green-100); +} + +.ci-status-icon-error, +.ci-status-icon-failed { + @include icon-styles($red-500, $red-100); +} + .ci-status-icon-pending, .ci-status-icon-waiting-for-resource, .ci-status-icon-failed-with-warnings, .ci-status-icon-success-with-warnings { - svg { - fill: $orange-500; - } - - &.interactive { - &:hover { - background: $orange-500; - - svg { - --svg-status-bg: #{$orange-100}; - box-shadow: 0 0 0 1px $orange-500; - } - } - } + @include icon-styles($orange-500, $orange-100); } .ci-status-icon-running { - svg { - fill: $blue-500; - } - - &.interactive { - &:hover { - background: $blue-500; - - svg { - --svg-status-bg: #{$blue-100}; - box-shadow: 0 0 0 1px $blue-500; - } - } - } + @include icon-styles($blue-500, $blue-100); } .ci-status-icon-canceled, .ci-status-icon-disabled, .ci-status-icon-scheduled, .ci-status-icon-manual { - svg { - fill: $gray-900; - } - - &.interactive { - &:hover { - background: $gray-900; - - svg { - --svg-status-bg: #{$gray-100}; - box-shadow: 0 0 0 1px $gray-900; - } - } - } + @include icon-styles($gray-900, $gray-100); } .ci-status-icon-notification, @@ -96,20 +57,7 @@ .ci-status-icon-created, .ci-status-icon-skipped, .ci-status-icon-notfound { - svg { - fill: $gray-500; - } - - &.interactive { - &:hover { - background: $gray-500; - - svg { - --svg-status-bg: #{$gray-100}; - box-shadow: 0 0 0 1px $gray-500; - } - } - } + @include icon-styles($gray-500, $gray-100); } .icon-link { diff --git a/app/assets/stylesheets/page_bundles/pipelines.scss b/app/assets/stylesheets/page_bundles/pipelines.scss index a225a0f0061..4946bbbebe5 100644 --- a/app/assets/stylesheets/page_bundles/pipelines.scss +++ b/app/assets/stylesheets/page_bundles/pipelines.scss @@ -74,11 +74,8 @@ .stage-cell { .stage-container { - align-items: center; - display: inline-flex; - - + .stage-container { - margin-left: 4px; + &:last-child { + margin-right: 0; } // Hack to show a button tooltip inline @@ -94,10 +91,11 @@ &:not(:last-child) { &::after { content: ''; - width: 4px; + border-bottom: 2px solid $gray-200; position: absolute; right: -4px; - border-bottom: 2px solid $gray-200; + top: 11px; + width: 4px; } } } diff --git a/app/controllers/projects/mattermosts_controller.rb b/app/controllers/projects/mattermosts_controller.rb index c4f4913a620..a4091ebdf4b 100644 --- a/app/controllers/projects/mattermosts_controller.rb +++ b/app/controllers/projects/mattermosts_controller.rb @@ -20,7 +20,7 @@ class Projects::MattermostsController < Projects::ApplicationController if result flash[:notice] = 'This service is now configured' - redirect_to edit_project_integration_path(@project, integration) + redirect_to edit_project_settings_integration_path(@project, integration) else flash[:alert] = message || 'Failed to configure service' redirect_to new_project_mattermost_path(@project) diff --git a/app/controllers/projects/prometheus/metrics_controller.rb b/app/controllers/projects/prometheus/metrics_controller.rb index f79fbd663a0..db5471ea322 100644 --- a/app/controllers/projects/prometheus/metrics_controller.rb +++ b/app/controllers/projects/prometheus/metrics_controller.rb @@ -67,7 +67,7 @@ module Projects ) if @metric.persisted? - redirect_to edit_project_integration_path(project, ::Integrations::Prometheus), + redirect_to edit_project_settings_integration_path(project, ::Integrations::Prometheus), notice: _('Metric was successfully added.') else render 'new' @@ -78,7 +78,7 @@ module Projects @metric = prometheus_metric if @metric.update(metrics_params) - redirect_to edit_project_integration_path(project, ::Integrations::Prometheus), + redirect_to edit_project_settings_integration_path(project, ::Integrations::Prometheus), notice: _('Metric was successfully updated.') else render 'edit' @@ -94,7 +94,7 @@ module Projects respond_to do |format| format.html do - redirect_to edit_project_integration_path(project, ::Integrations::Prometheus), status: :see_other + redirect_to edit_project_settings_integration_path(project, ::Integrations::Prometheus), status: :see_other end format.json do head :ok diff --git a/app/controllers/projects/service_hook_logs_controller.rb b/app/controllers/projects/service_hook_logs_controller.rb deleted file mode 100644 index 7b037c60321..00000000000 --- a/app/controllers/projects/service_hook_logs_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -class Projects::ServiceHookLogsController < Projects::HookLogsController - extend Gitlab::Utils::Override - - before_action :integration, only: [:show, :retry] - - def retry - execute_hook - redirect_to edit_project_integration_path(@project, @integration) - end - - private - - def integration - @integration ||= @project.find_or_initialize_integration(params[:integration_id]) - end - - override :hook - def hook - @hook ||= integration.service_hook || not_found - end -end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb deleted file mode 100644 index 8f83e34411b..00000000000 --- a/app/controllers/projects/services_controller.rb +++ /dev/null @@ -1,122 +0,0 @@ -# frozen_string_literal: true - -class Projects::ServicesController < Projects::ApplicationController - include Integrations::Params - include InternalRedirect - - # Authorize - before_action :authorize_admin_project! - before_action :ensure_service_enabled - before_action :integration - before_action :default_integration, only: [:edit, :update] - before_action :web_hook_logs, only: [:edit, :update] - - respond_to :html - - layout "project_settings" - - feature_category :integrations - urgency :low, [:test] - - def edit - end - - def update - attributes = integration_params[:integration] - - if use_inherited_settings?(attributes) - integration.inherit_from_id = default_integration.id - - if saved = integration.save(context: :manual_change) - BulkUpdateIntegrationService.new(default_integration, [integration]).execute - end - else - attributes[:inherit_from_id] = nil - integration.attributes = attributes - saved = integration.save(context: :manual_change) - end - - respond_to do |format| - format.html do - if saved - redirect_to redirect_path, notice: success_message - else - render 'edit' - end - end - - format.json do - status = saved ? :ok : :unprocessable_entity - - render json: serialize_as_json, status: status - end - end - end - - def test - if integration.testable? - render json: service_test_response, status: :ok - else - render json: {}, status: :not_found - end - end - - private - - def redirect_path - safe_redirect_path(params[:redirect_to]).presence || edit_project_integration_path(project, integration) - end - - def service_test_response - unless integration.update(integration_params[:integration]) - return { error: true, message: _('Validations failed.'), service_response: integration.errors.full_messages.join(','), test_failed: false } - end - - result = ::Integrations::Test::ProjectService.new(integration, current_user, params[:event]).execute - - unless result[:success] - return { error: true, message: s_('Integrations|Connection failed. Please check your settings.'), service_response: result[:message].to_s, test_failed: true } - end - - result[:data].presence || {} - rescue *Gitlab::HTTP::HTTP_ERRORS => e - { error: true, message: s_('Integrations|Connection failed. Please check your settings.'), service_response: e.message, test_failed: true } - end - - def success_message - if integration.active? - s_('Integrations|%{integration} settings saved and active.') % { integration: integration.title } - else - s_('Integrations|%{integration} settings saved, but not active.') % { integration: integration.title } - end - end - - def integration - @integration ||= project.find_or_initialize_integration(params[:id]) - end - alias_method :service, :integration - - def default_integration - @default_integration ||= Integration.default_integration(integration.type, project) - end - - def web_hook_logs - return unless integration.service_hook.present? - - @web_hook_logs ||= integration.service_hook.web_hook_logs.recent.page(params[:page]) - end - - def ensure_service_enabled - render_404 unless service - end - - def serialize_as_json - integration - .as_json(only: integration.json_fields) - .merge(errors: integration.errors.as_json) - end - - def use_inherited_settings?(attributes) - default_integration && attributes[:inherit_from_id] == default_integration.id.to_s - end -end diff --git a/app/controllers/projects/settings/integration_hook_logs_controller.rb b/app/controllers/projects/settings/integration_hook_logs_controller.rb new file mode 100644 index 00000000000..b3b5a292d42 --- /dev/null +++ b/app/controllers/projects/settings/integration_hook_logs_controller.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Projects + module Settings + class IntegrationHookLogsController < Projects::HookLogsController + extend Gitlab::Utils::Override + + before_action :integration, only: [:show, :retry] + + def retry + execute_hook + redirect_to edit_project_settings_integration_path(@project, @integration) + end + + private + + def integration + @integration ||= @project.find_or_initialize_integration(params[:integration_id]) + end + + override :hook + def hook + @hook ||= integration.service_hook || not_found + end + end + end +end diff --git a/app/controllers/projects/settings/integrations_controller.rb b/app/controllers/projects/settings/integrations_controller.rb index c9d92d1aee9..3365da65de8 100644 --- a/app/controllers/projects/settings/integrations_controller.rb +++ b/app/controllers/projects/settings/integrations_controller.rb @@ -3,14 +3,142 @@ module Projects module Settings class IntegrationsController < Projects::ApplicationController + include ::Integrations::Params + include ::InternalRedirect + before_action :authorize_admin_project! + before_action :ensure_integration_enabled, only: [:edit, :update, :test] + before_action :integration, only: [:edit, :update, :test] + before_action :default_integration, only: [:edit, :update] + before_action :web_hook_logs, only: [:edit, :update] + + respond_to :html + layout "project_settings" feature_category :integrations + urgency :low, [:test] - def show + def index @integrations = @project.find_or_initialize_integrations end + + def edit + end + + def update + attributes = integration_params[:integration] + + if use_inherited_settings?(attributes) + integration.inherit_from_id = default_integration.id + + if saved = integration.save(context: :manual_change) + BulkUpdateIntegrationService.new(default_integration, [integration]).execute + end + else + attributes[:inherit_from_id] = nil + integration.attributes = attributes + saved = integration.save(context: :manual_change) + end + + respond_to do |format| + format.html do + if saved + redirect_to redirect_path, notice: success_message + else + render 'edit' + end + end + + format.json do + status = saved ? :ok : :unprocessable_entity + + render json: serialize_as_json, status: status + end + end + end + + def test + if integration.testable? + render json: integration_test_response, status: :ok + else + render json: {}, status: :not_found + end + end + + private + + def redirect_path + safe_redirect_path(params[:redirect_to]).presence || + edit_project_settings_integration_path(project, integration) + end + + def integration_test_response + unless integration.update(integration_params[:integration]) + return { + error: true, + message: _('Validations failed.'), + service_response: integration.errors.full_messages.join(','), + test_failed: false + } + end + + result = ::Integrations::Test::ProjectService.new(integration, current_user, params[:event]).execute + + unless result[:success] + return { + error: true, + message: s_('Integrations|Connection failed. Please check your settings.'), + service_response: result[:message].to_s, + test_failed: true + } + end + + result[:data].presence || {} + rescue *Gitlab::HTTP::HTTP_ERRORS => e + { + error: true, + message: s_('Integrations|Connection failed. Please check your settings.'), + service_response: e.message, + test_failed: true + } + end + + def success_message + if integration.active? + format(s_('Integrations|%{integration} settings saved and active.'), integration: integration.title) + else + format(s_('Integrations|%{integration} settings saved, but not active.'), integration: integration.title) + end + end + + def integration + @integration ||= project.find_or_initialize_integration(params[:id]) + end + + def default_integration + @default_integration ||= Integration.default_integration(integration.type, project) + end + + def web_hook_logs + return unless integration.service_hook.present? + + @web_hook_logs ||= integration.service_hook.web_hook_logs.recent.page(params[:page]) + end + + def ensure_integration_enabled + render_404 unless integration + end + + def serialize_as_json + integration + .as_json(only: integration.json_fields) + .merge(errors: integration.errors.as_json) + end + + def use_inherited_settings?(attributes) + default_integration && attributes[:inherit_from_id] == default_integration.id.to_s + end end end end diff --git a/app/helpers/custom_metrics_helper.rb b/app/helpers/custom_metrics_helper.rb index 5442120008a..8a9d94bd2a1 100644 --- a/app/helpers/custom_metrics_helper.rb +++ b/app/helpers/custom_metrics_helper.rb @@ -5,7 +5,7 @@ module CustomMetricsHelper { 'custom-metrics-path' => url_for([project, metric]), 'metric-persisted' => metric.persisted?.to_s, - 'edit-project-service-path' => edit_project_integration_path(project, ::Integrations::Prometheus), + 'edit-integration-path' => edit_project_settings_integration_path(project, ::Integrations::Prometheus), 'validate-query-path' => validate_query_project_prometheus_metrics_path(project), 'title' => metric.title.to_s, 'query' => metric.query.to_s, diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb index 3b60bda8605..119eaa88bd1 100644 --- a/app/helpers/environments_helper.rb +++ b/app/helpers/environments_helper.rb @@ -59,7 +59,7 @@ module EnvironmentsHelper return {} unless project { - 'settings_path' => edit_project_integration_path(project, 'prometheus'), + 'settings_path' => edit_project_settings_integration_path(project, 'prometheus'), 'clusters_path' => project_clusters_path(project), 'dashboards_endpoint' => project_performance_monitoring_dashboards_path(project, format: :json), 'default_branch' => project.default_branch, diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index 862938ac961..44e0307fd81 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -58,7 +58,7 @@ module IntegrationsHelper def scoped_integration_path(integration, project: nil, group: nil) if project.present? - project_integration_path(project, integration) + project_settings_integration_path(project, integration) elsif group.present? group_settings_integration_path(group, integration) else @@ -68,7 +68,7 @@ module IntegrationsHelper def scoped_edit_integration_path(integration, project: nil, group: nil) if project.present? - edit_project_integration_path(project, integration) + edit_project_settings_integration_path(project, integration) elsif group.present? edit_group_settings_integration_path(group, integration) else @@ -82,7 +82,7 @@ module IntegrationsHelper def scoped_test_integration_path(integration, project: nil, group: nil) if project.present? - test_project_integration_path(project, integration) + test_project_settings_integration_path(project, integration) elsif group.present? test_group_settings_integration_path(group, integration) else diff --git a/app/presenters/service_hook_presenter.rb b/app/presenters/service_hook_presenter.rb index f2a06358918..b34679c85cf 100644 --- a/app/presenters/service_hook_presenter.rb +++ b/app/presenters/service_hook_presenter.rb @@ -4,10 +4,10 @@ class ServiceHookPresenter < Gitlab::View::Presenter::Delegated presents ::ServiceHook, as: :service_hook def logs_details_path(log) - project_integration_hook_log_path(integration.project, integration, log) + project_settings_integration_hook_log_path(integration.project, integration, log) end def logs_retry_path(log) - retry_project_integration_hook_log_path(integration.project, integration, log) + retry_project_settings_integration_hook_log_path(integration.project, integration, log) end end diff --git a/app/views/clusters/clusters/_advanced_settings.html.haml b/app/views/clusters/clusters/_advanced_settings.html.haml index 59c8fe04b09..8eba398fd13 100644 --- a/app/views/clusters/clusters/_advanced_settings.html.haml +++ b/app/views/clusters/clusters/_advanced_settings.html.haml @@ -35,7 +35,7 @@ = s_("ClusterIntegration|This is necessary if your integration has become out of sync. The cache is repopulated during the next CI job that requires namespace and service accounts.") - else = s_("ClusterIntegration|This is necessary to clear existing environment-namespace associations from clusters previously managed by GitLab.") - = link_to(s_('ClusterIntegration|Clear cluster cache'), clusterable.clear_cluster_cache_path(@cluster), method: :delete, class: 'btn gl-button btn-info') + = link_to(s_('ClusterIntegration|Clear cluster cache'), clusterable.clear_cluster_cache_path(@cluster), method: :delete, class: 'btn gl-button btn-confirm') .sub-section.form-group %h4.text-danger diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml index 2c46bf1e074..0b45869bdf9 100644 --- a/app/views/profiles/chat_names/_chat_name.html.haml +++ b/app/views/profiles/chat_names/_chat_name.html.haml @@ -10,7 +10,7 @@ %td %strong - if can?(current_user, :admin_project, project) - = link_to integration.title, edit_project_integration_path(project, integration) + = link_to integration.title, edit_project_settings_integration_path(project, integration) - else = integration.title %td diff --git a/app/views/projects/import/jira/show.html.haml b/app/views/projects/import/jira/show.html.haml index 1feae7baa02..2605ebc544f 100644 --- a/app/views/projects/import/jira/show.html.haml +++ b/app/views/projects/import/jira/show.html.haml @@ -1,6 +1,6 @@ .js-jira-import-root{ data: { project_path: @project.full_path, issues_path: project_issues_path(@project), - jira_integration_path: edit_project_integration_path(@project, :jira), + jira_integration_path: edit_project_settings_integration_path(@project, :jira), is_jira_configured: @project.jira_integration&.configured?.to_s, in_progress_illustration: image_path('illustrations/export-import.svg'), project_id: @project.id, diff --git a/app/views/projects/mattermosts/_no_teams.html.haml b/app/views/projects/mattermosts/_no_teams.html.haml index 1f008496a34..5886c0565b1 100644 --- a/app/views/projects/mattermosts/_no_teams.html.haml +++ b/app/views/projects/mattermosts/_no_teams.html.haml @@ -9,4 +9,4 @@ and try again. %hr .clearfix - = link_to 'Go back', edit_project_integration_path(@project, @integration), class: 'gl-button btn btn-lg float-right' + = link_to 'Go back', edit_project_settings_integration_path(@project, @integration), class: 'gl-button btn btn-lg float-right' diff --git a/app/views/projects/mattermosts/_team_selection.html.haml b/app/views/projects/mattermosts/_team_selection.html.haml index d52d980c364..e6f7833d16a 100644 --- a/app/views/projects/mattermosts/_team_selection.html.haml +++ b/app/views/projects/mattermosts/_team_selection.html.haml @@ -42,5 +42,5 @@ %hr .clearfix .float-right - = link_to _('Cancel'), edit_project_integration_path(@project, @integration), class: 'gl-button btn btn-lg' + = link_to _('Cancel'), edit_project_settings_integration_path(@project, @integration), class: 'gl-button btn btn-lg' = f.submit 'Install', class: 'gl-button btn btn-success btn-lg' diff --git a/app/views/projects/prometheus/metrics/edit.html.haml b/app/views/projects/prometheus/metrics/edit.html.haml index 146bf6b6853..212d625d292 100644 --- a/app/views/projects/prometheus/metrics/edit.html.haml +++ b/app/views/projects/prometheus/metrics/edit.html.haml @@ -1,6 +1,6 @@ - add_to_breadcrumbs _("Settings"), edit_project_path(@project) - add_to_breadcrumbs _("Integrations"), project_settings_integrations_path(@project) -- add_to_breadcrumbs "Prometheus", edit_project_integration_path(@project, ::Integrations::Prometheus) +- add_to_breadcrumbs "Prometheus", edit_project_settings_integration_path(@project, ::Integrations::Prometheus) - breadcrumb_title s_('Metrics|Edit metric') - page_title @metric.title, s_('Metrics|Edit metric') = render 'form', project: @project, metric: @metric diff --git a/app/views/projects/prometheus/metrics/new.html.haml b/app/views/projects/prometheus/metrics/new.html.haml index ad8463d1804..c04e5f385d9 100644 --- a/app/views/projects/prometheus/metrics/new.html.haml +++ b/app/views/projects/prometheus/metrics/new.html.haml @@ -1,6 +1,6 @@ - add_to_breadcrumbs _("Settings"), edit_project_path(@project) - add_to_breadcrumbs _("Integrations"), project_settings_integrations_path(@project) -- add_to_breadcrumbs "Prometheus", edit_project_integration_path(@project, ::Integrations::Prometheus) +- add_to_breadcrumbs "Prometheus", edit_project_settings_integration_path(@project, ::Integrations::Prometheus) - breadcrumb_title s_('Metrics|New metric') - page_title s_('Metrics|New metric') = render 'form', project: @project, metric: @metric diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/settings/integrations/_form.html.haml index 9d74f99bb19..9d74f99bb19 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/settings/integrations/_form.html.haml diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/settings/integrations/edit.html.haml index a250daafdbb..a250daafdbb 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/settings/integrations/edit.html.haml diff --git a/app/views/projects/settings/integrations/show.html.haml b/app/views/projects/settings/integrations/index.html.haml index 84635941436..84635941436 100644 --- a/app/views/projects/settings/integrations/show.html.haml +++ b/app/views/projects/settings/integrations/index.html.haml diff --git a/app/views/shared/empty_states/_wikis.html.haml b/app/views/shared/empty_states/_wikis.html.haml index c3120774826..552b100d5dd 100644 --- a/app/views/shared/empty_states/_wikis.html.haml +++ b/app/views/shared/empty_states/_wikis.html.haml @@ -13,7 +13,7 @@ = create_link - if show_enable_confluence_integration?(@wiki.container) = link_to s_('WikiEmpty|Enable the Confluence Wiki integration'), - edit_project_integration_path(@project, :confluence), + edit_project_settings_integration_path(@project, :confluence), class: 'btn gl-button', title: s_('WikiEmpty|Enable the Confluence Wiki integration') - elsif @project && can?(current_user, :read_issue, @project) |