Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-05-25 21:08:15 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-25 21:08:15 +0300
commit28b119a4b47d3a41c4879aab651221b85289bc69 (patch)
tree5482e008b585e7170a54f7e67e0e62bdb091b7f5 /app
parent4dc41ac252c0bfefb9bc55a8627262cc76c69d5e (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js2
-rw-r--r--app/assets/javascripts/clusters/components/remove_cluster_confirmation.vue51
-rw-r--r--app/assets/javascripts/content_editor/extensions/sourcemap.js2
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js8
-rw-r--r--app/assets/javascripts/content_editor/services/remark_markdown_deserializer.js4
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js43
-rw-r--r--app/assets/javascripts/custom_metrics/components/custom_metrics_form.vue4
-rw-r--r--app/assets/javascripts/custom_metrics/index.js4
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue7
-rw-r--r--app/assets/javascripts/lib/gfm/index.js7
-rw-r--r--app/assets/javascripts/lib/utils/users_cache.js7
-rw-r--r--app/assets/javascripts/members/components/table/members_table.vue4
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue2
-rw-r--r--app/assets/javascripts/pages/projects/settings/integrations/edit/index.js (renamed from app/assets/javascripts/pages/projects/services/edit/index.js)0
-rw-r--r--app/assets/javascripts/pages/projects/settings/integrations/index/index.js (renamed from app/assets/javascripts/pages/projects/settings/integrations/show/index.js)0
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_mini_graph.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue25
-rw-r--r--app/assets/javascripts/user_popovers.js139
-rw-r--r--app/assets/javascripts/vue_shared/alert_details/components/alert_details.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/ci_icon.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/constants.js1
-rw-r--r--app/assets/javascripts/vue_shared/components/user_popover/user_popover.vue18
-rw-r--r--app/assets/stylesheets/framework/icons.scss104
-rw-r--r--app/assets/stylesheets/page_bundles/pipelines.scss12
-rw-r--r--app/controllers/projects/mattermosts_controller.rb2
-rw-r--r--app/controllers/projects/prometheus/metrics_controller.rb6
-rw-r--r--app/controllers/projects/service_hook_logs_controller.rb23
-rw-r--r--app/controllers/projects/services_controller.rb122
-rw-r--r--app/controllers/projects/settings/integration_hook_logs_controller.rb27
-rw-r--r--app/controllers/projects/settings/integrations_controller.rb130
-rw-r--r--app/helpers/custom_metrics_helper.rb2
-rw-r--r--app/helpers/environments_helper.rb2
-rw-r--r--app/helpers/integrations_helper.rb6
-rw-r--r--app/presenters/service_hook_presenter.rb4
-rw-r--r--app/views/clusters/clusters/_advanced_settings.html.haml2
-rw-r--r--app/views/profiles/chat_names/_chat_name.html.haml2
-rw-r--r--app/views/projects/import/jira/show.html.haml2
-rw-r--r--app/views/projects/mattermosts/_no_teams.html.haml2
-rw-r--r--app/views/projects/mattermosts/_team_selection.html.haml2
-rw-r--r--app/views/projects/prometheus/metrics/edit.html.haml2
-rw-r--r--app/views/projects/prometheus/metrics/new.html.haml2
-rw-r--r--app/views/projects/settings/integrations/_form.html.haml (renamed from app/views/projects/services/_form.html.haml)0
-rw-r--r--app/views/projects/settings/integrations/edit.html.haml (renamed from app/views/projects/services/edit.html.haml)0
-rw-r--r--app/views/projects/settings/integrations/index.html.haml (renamed from app/views/projects/settings/integrations/show.html.haml)0
-rw-r--r--app/views/shared/empty_states/_wikis.html.haml2
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(/^&#x000A;/, '');
},
},
- 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)