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>2023-11-22 15:10:30 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-11-22 15:10:30 +0300
commit49203bfa3c7eb607a7561ae7da9b5c52aa49fd77 (patch)
treef33cd54ec9a45d69a3e58fe93735070d3b718913 /app
parent3c9a2dd62025043448c9ea9a6df86422874ee4be (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue15
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_add_note.vue138
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue136
-rw-r--r--app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue7
-rw-r--r--app/assets/javascripts/admin/abuse_report/index.js1
-rw-r--r--app/assets/javascripts/ci/pipeline_details/pipelines_index.js2
-rw-r--r--app/assets/javascripts/ci/pipeline_editor/constants.js11
-rw-r--r--app/assets/javascripts/ci/pipelines_page/components/empty_state/pipelines_ci_templates.vue97
-rw-r--r--app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue8
-rw-r--r--app/assets/javascripts/header.js50
-rw-r--r--app/assets/javascripts/main.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/list_selector/constants.js24
-rw-r--r--app/assets/javascripts/vue_shared/components/list_selector/deploy_key_item.vue51
-rw-r--r--app/assets/javascripts/vue_shared/components/list_selector/index.vue34
-rw-r--r--app/helpers/ci/pipelines_helper.rb10
-rw-r--r--app/models/abuse_report.rb2
-rw-r--r--app/models/ci/catalog/listing.rb3
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/serializers/admin/abuse_report_details_entity.rb4
19 files changed, 496 insertions, 103 deletions
diff --git a/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue b/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
index 80af7d7400a..29de7e1ad1d 100644
--- a/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/abuse_report_notes.vue
@@ -6,6 +6,7 @@ import SkeletonLoadingContainer from '~/vue_shared/components/notes/skeleton_not
import { SKELETON_NOTES_COUNT } from '~/admin/abuse_report/constants';
import abuseReportNotesQuery from '../graphql/notes/abuse_report_notes.query.graphql';
import AbuseReportDiscussion from './notes/abuse_report_discussion.vue';
+import AbuseReportAddNote from './notes/abuse_report_add_note.vue';
export default {
name: 'AbuseReportNotes',
@@ -16,6 +17,7 @@ export default {
components: {
SkeletonLoadingContainer,
AbuseReportDiscussion,
+ AbuseReportAddNote,
},
props: {
abuseReportId: {
@@ -60,6 +62,9 @@ export default {
const discussionId = discussion.notes.nodes[0].id;
return discussionId.split('/')[discussionId.split('/').length - 1];
},
+ updateKey() {
+ this.addNoteKey = uniqueId(`abuse-report-add-note-${this.abuseReportId}`);
+ },
},
};
</script>
@@ -86,6 +91,16 @@ export default {
:abuse-report-id="abuseReportId"
/>
</ul>
+ <div class="js-comment-form">
+ <ul class="notes notes-form timeline">
+ <abuse-report-add-note
+ :key="addNoteKey"
+ :is-new-discussion="true"
+ :abuse-report-id="abuseReportId"
+ @cancelEditing="updateKey"
+ />
+ </ul>
+ </div>
</template>
</div>
</div>
diff --git a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_add_note.vue b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_add_note.vue
new file mode 100644
index 00000000000..3c709fc565f
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_add_note.vue
@@ -0,0 +1,138 @@
+<script>
+import { sprintf, __ } from '~/locale';
+import { createAlert } from '~/alert';
+import { clearDraft } from '~/lib/utils/autosave';
+import createNoteMutation from '../../graphql/notes/create_abuse_report_note.mutation.graphql';
+import AbuseReportCommentForm from './abuse_report_comment_form.vue';
+
+export default {
+ name: 'AbuseReportAddNote',
+ i18n: {
+ reply: __('Reply'),
+ replyToComment: __('Reply to comment'),
+ commentError: __('Your comment could not be submitted because %{reason}.'),
+ genericError: __(
+ 'Your comment could not be submitted! Please check your network connection and try again.',
+ ),
+ },
+ components: {
+ AbuseReportCommentForm,
+ },
+ props: {
+ abuseReportId: {
+ type: String,
+ required: true,
+ },
+ discussionId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ isNewDiscussion: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ isEditing: this.isNewDiscussion,
+ isSubmitting: false,
+ };
+ },
+ computed: {
+ autosaveKey() {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ return this.discussionId ? `${this.discussionId}-comment` : `${this.abuseReportId}-comment`;
+ },
+ timelineEntryClasses() {
+ return this.isNewDiscussion
+ ? 'timeline-entry note-form'
+ : // eslint-disable-next-line @gitlab/require-i18n-strings
+ 'note note-wrapper note-comment discussion-reply-holder gl-border-t-0! clearfix';
+ },
+ timelineEntryInnerClasses() {
+ return this.isNewDiscussion ? 'timeline-entry-inner' : '';
+ },
+ commentFormWrapperClasses() {
+ return !this.isEditing
+ ? 'gl-relative gl-display-flex gl-align-items-flex-start gl-flex-nowrap'
+ : '';
+ },
+ },
+ methods: {
+ async addNote({ commentText }) {
+ this.isSubmitting = true;
+
+ this.$apollo
+ .mutate({
+ mutation: createNoteMutation,
+ variables: {
+ input: {
+ noteableId: this.abuseReportId,
+ body: commentText,
+ discussionId: this.discussionId || null,
+ },
+ },
+ })
+ .then(() => {
+ clearDraft(this.autosaveKey);
+ this.cancelEditing();
+ })
+ .catch((error) => {
+ const errorMessage = error?.message
+ ? sprintf(this.$options.i18n.commentError, { reason: error.message.toLowerCase() })
+ : this.$options.i18n.genericError;
+
+ createAlert({
+ message: errorMessage,
+ parent: this.$el,
+ captureError: true,
+ });
+ })
+ .finally(() => {
+ this.isSubmitting = false;
+ });
+ },
+ cancelEditing() {
+ this.isEditing = this.isNewDiscussion;
+ this.$emit('cancelEditing');
+ },
+ showReplyForm() {
+ this.isEditing = true;
+ },
+ },
+};
+</script>
+
+<template>
+ <li :class="timelineEntryClasses" data-testid="abuse-report-note-timeline-entry">
+ <div :class="timelineEntryInnerClasses" data-testid="abuse-report-note-timeline-entry-inner">
+ <div class="timeline-content">
+ <div class="flash-container"></div>
+ <div :class="commentFormWrapperClasses" data-testid="abuse-report-comment-form-wrapper">
+ <abuse-report-comment-form
+ v-if="isEditing"
+ :abuse-report-id="abuseReportId"
+ :is-submitting="isSubmitting"
+ :autosave-key="autosaveKey"
+ :is-new-discussion="isNewDiscussion"
+ @submitForm="addNote"
+ @cancelEditing="cancelEditing"
+ />
+ <textarea
+ v-else
+ ref="textarea"
+ rows="1"
+ class="reply-placeholder-text-field gl-font-regular!"
+ data-testid="abuse-report-note-reply-textarea"
+ :placeholder="$options.i18n.reply"
+ :aria-label="$options.i18n.replyToComment"
+ @focus="showReplyForm"
+ @click="showReplyForm"
+ ></textarea>
+ </div>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue
new file mode 100644
index 00000000000..646ffc690d0
--- /dev/null
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_comment_form.vue
@@ -0,0 +1,136 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { getDraft, clearDraft, updateDraft } from '~/lib/utils/autosave';
+import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
+import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
+
+export default {
+ name: 'AbuseReportCommentForm',
+ i18n: {
+ addReplyText: __('Add a reply'),
+ placeholderText: __('Write a comment or drag your files hereā€¦'),
+ cancelButtonText: __('Cancel'),
+ confirmText: s__('Notes|Are you sure you want to cancel creating this comment?'),
+ discardText: __('Discard changes'),
+ continueEditingText: __('Continue editing'),
+ },
+ components: {
+ GlButton,
+ MarkdownEditor,
+ },
+ inject: ['uploadNoteAttachmentPath'],
+ props: {
+ abuseReportId: {
+ type: String,
+ required: true,
+ },
+ isSubmitting: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ autosaveKey: {
+ type: String,
+ required: true,
+ },
+ isNewDiscussion: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ initialValue: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ commentText: getDraft(this.autosaveKey) || this.initialValue || '',
+ };
+ },
+ computed: {
+ formFieldProps() {
+ return {
+ 'aria-label': this.$options.i18n.addReplyText,
+ placeholder: this.$options.i18n.placeholderText,
+ id: 'abuse-report-add-or-edit-comment',
+ name: 'abuse-report-add-or-edit-comment',
+ };
+ },
+ markdownDocsPath() {
+ return helpPagePath('user/markdown');
+ },
+ commentButtonText() {
+ return this.isNewDiscussion ? __('Comment') : __('Reply');
+ },
+ },
+ methods: {
+ setCommentText(newText) {
+ if (!this.isSubmitting) {
+ this.commentText = newText;
+ updateDraft(this.autosaveKey, this.commentText);
+ }
+ },
+ async cancelEditing() {
+ if (this.commentText && this.commentText !== this.initialValue) {
+ const confirmed = await confirmAction(this.$options.i18n.confirmText, {
+ primaryBtnText: this.$options.i18n.discardText,
+ cancelBtnText: this.$options.i18n.continueEditingText,
+ primaryBtnVariant: 'danger',
+ });
+
+ if (!confirmed) {
+ return;
+ }
+ }
+
+ this.$emit('cancelEditing');
+ clearDraft(this.autosaveKey);
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="timeline-discussion-body gl-overflow-visible!">
+ <div class="note-body gl-p-0! gl-overflow-visible!">
+ <form class="common-note-form gfm-form js-main-target-form gl-flex-grow-1 new-note">
+ <markdown-editor
+ :value="commentText"
+ :enable-content-editor="false"
+ render-markdown-path=""
+ :uploads-path="uploadNoteAttachmentPath"
+ :markdown-docs-path="markdownDocsPath"
+ :form-field-props="formFieldProps"
+ :autofocus="true"
+ @input="setCommentText"
+ @keydown.meta.enter="$emit('submitForm', { commentText })"
+ @keydown.ctrl.enter="$emit('submitForm', { commentText })"
+ @keydown.esc.stop="cancelEditing"
+ />
+ <div class="note-form-actions">
+ <gl-button
+ category="primary"
+ variant="confirm"
+ data-testid="comment-button"
+ :disabled="!commentText.length"
+ :loading="isSubmitting"
+ @click="$emit('submitForm', { commentText })"
+ >
+ {{ commentButtonText }}
+ </gl-button>
+ <gl-button
+ data-testid="cancel-button"
+ category="primary"
+ class="gl-ml-3"
+ @click="cancelEditing"
+ >{{ $options.i18n.cancelButtonText }}
+ </gl-button>
+ </div>
+ </form>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue
index 4d24471fa43..49ba777f697 100644
--- a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_discussion.vue
@@ -4,6 +4,7 @@ import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item
import DiscussionNotesRepliesWrapper from '~/notes/components/discussion_notes_replies_wrapper.vue';
import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
import AbuseReportNote from './abuse_report_note.vue';
+import AbuseReportAddNote from './abuse_report_add_note.vue';
export default {
name: 'AbuseReportDiscussion',
@@ -12,6 +13,7 @@ export default {
DiscussionNotesRepliesWrapper,
ToggleRepliesWidget,
AbuseReportNote,
+ AbuseReportAddNote,
},
props: {
abuseReportId: {
@@ -92,6 +94,11 @@ export default {
:abuse-report-id="abuseReportId"
/>
</template>
+ <abuse-report-add-note
+ :discussion-id="discussionId"
+ :is-new-discussion="false"
+ :abuse-report-id="abuseReportId"
+ />
</template>
</discussion-notes-replies-wrapper>
</ul>
diff --git a/app/assets/javascripts/admin/abuse_report/index.js b/app/assets/javascripts/admin/abuse_report/index.js
index c2117130d26..e4319b3edef 100644
--- a/app/assets/javascripts/admin/abuse_report/index.js
+++ b/app/assets/javascripts/admin/abuse_report/index.js
@@ -30,6 +30,7 @@ export const initAbuseReportApp = () => {
allowScopedLabels: false,
updatePath: abuseReport.report.updatePath,
listPath: abuseReportsListPath,
+ uploadNoteAttachmentPath: abuseReport.uploadNoteAttachmentPath,
labelsManagePath: '',
allowLabelCreate: true,
},
diff --git a/app/assets/javascripts/ci/pipeline_details/pipelines_index.js b/app/assets/javascripts/ci/pipeline_details/pipelines_index.js
index ea2875713a9..b4528ab895d 100644
--- a/app/assets/javascripts/ci/pipeline_details/pipelines_index.js
+++ b/app/assets/javascripts/ci/pipeline_details/pipelines_index.js
@@ -44,6 +44,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
params,
fullPath,
visibilityPipelineIdType,
+ showJenkinsCiPrompt,
} = el.dataset;
return new Vue({
@@ -57,6 +58,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => {
pipelineEditorPath,
pipelineSchedulesPath,
suggestedCiTemplates: JSON.parse(suggestedCiTemplates),
+ showJenkinsCiPrompt: parseBoolean(showJenkinsCiPrompt),
},
data() {
return {
diff --git a/app/assets/javascripts/ci/pipeline_editor/constants.js b/app/assets/javascripts/ci/pipeline_editor/constants.js
index e85138e361f..66725df15f0 100644
--- a/app/assets/javascripts/ci/pipeline_editor/constants.js
+++ b/app/assets/javascripts/ci/pipeline_editor/constants.js
@@ -1,4 +1,5 @@
import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
export const EDITOR_APP_DRAWER_HELP = 'HELP';
export const EDITOR_APP_DRAWER_JOB_ASSISTANT = 'JOB_ASSISTANT';
@@ -93,6 +94,9 @@ export const VALIDATE_TAB_FEEDBACK_URL = 'https://gitlab.com/gitlab-org/gitlab/-
export const COMMIT_SHA_POLL_INTERVAL = 1000;
+export const MIGRATION_PLAN_HELP_PATH = helpPagePath('ci/migration/plan_a_migration');
+export const MIGRATE_FROM_JENKINS_TRACKING_LABEL = 'migrate_from_jenkins_prompt';
+
export const I18N = {
title: s__('Pipelines|Get started with GitLab CI/CD'),
learnBasics: {
@@ -107,6 +111,13 @@ export const I18N = {
),
cta: s__('Pipelines|Try test template'),
},
+ migrateFromJenkins: {
+ title: s__('Pipelines|Migrate to GitLab CI/CD from Jenkins'),
+ description: s__(
+ 'Pipelines|Take advantage of simple, scalable pipelines and CI/CD-enabled features. You can view integration results, security scans, tests, code coverage and more directly in merge requests!',
+ ),
+ cta: s__('Pipelines|Start with a migration plan'),
+ },
},
templates: {
title: s__('Pipelines|Ready to set up CI/CD for your project?'),
diff --git a/app/assets/javascripts/ci/pipelines_page/components/empty_state/pipelines_ci_templates.vue b/app/assets/javascripts/ci/pipelines_page/components/empty_state/pipelines_ci_templates.vue
index a6297213402..c9631d8f36b 100644
--- a/app/assets/javascripts/ci/pipelines_page/components/empty_state/pipelines_ci_templates.vue
+++ b/app/assets/javascripts/ci/pipelines_page/components/empty_state/pipelines_ci_templates.vue
@@ -1,7 +1,12 @@
<script>
import { GlButton, GlCard, GlSprintf } from '@gitlab/ui';
import { mergeUrlParams } from '~/lib/utils/url_utility';
-import { STARTER_TEMPLATE_NAME, I18N } from '~/ci/pipeline_editor/constants';
+import {
+ STARTER_TEMPLATE_NAME,
+ I18N,
+ MIGRATION_PLAN_HELP_PATH,
+ MIGRATE_FROM_JENKINS_TRACKING_LABEL,
+} from '~/ci/pipeline_editor/constants';
import Tracking from '~/tracking';
import CiTemplates from './ci_templates.vue';
@@ -15,7 +20,7 @@ export default {
mixins: [Tracking.mixin()],
STARTER_TEMPLATE_NAME,
I18N,
- inject: ['pipelineEditorPath'],
+ inject: ['pipelineEditorPath', 'showJenkinsCiPrompt'],
data() {
return {
gettingStartedTemplateUrl: mergeUrlParams(
@@ -23,17 +28,23 @@ export default {
this.pipelineEditorPath,
),
tracker: null,
+ migrationPlanUrl: MIGRATION_PLAN_HELP_PATH,
+ migrationPromptTrackingLabel: MIGRATE_FROM_JENKINS_TRACKING_LABEL,
};
},
+ mounted() {
+ if (this.showJenkinsCiPrompt) {
+ this.trackEvent('render', this.migrationPromptTrackingLabel);
+ }
+ },
methods: {
- trackEvent(template) {
- this.track('template_clicked', {
- label: template,
- });
+ trackEvent(action, label) {
+ this.track(action, { label });
},
},
};
</script>
+
<template>
<div>
<h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.I18N.title }}</h2>
@@ -47,28 +58,62 @@ export default {
</gl-sprintf>
</p>
- <div class="gl-lg-w-25p gl-lg-pr-5 gl-mb-8">
- <gl-card>
- <div class="gl-flex-direction-row">
- <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>
- <div class="gl-mb-3">
- <strong class="gl-text-gray-800 gl-mb-2">
- {{ $options.I18N.learnBasics.gettingStarted.title }}
- </strong>
+ <div class="gl-display-flex gl-flex-direction-row gl-flex-wrap">
+ <div
+ v-if="showJenkinsCiPrompt"
+ class="gl-lg-w-25p gl-md-w-half gl-w-full gl-md-pr-5 gl-pb-8"
+ data-testid="migrate-from-jenkins-prompt"
+ >
+ <gl-card class="gl-bg-blue-50">
+ <div class="gl-flex-direction-row">
+ <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="rocket" /></div>
+ <div class="gl-mb-3">
+ <strong class="gl-text-gray-800 gl-mb-2">{{
+ $options.I18N.learnBasics.migrateFromJenkins.title
+ }}</strong>
+ </div>
+ <p class="gl-font-sm gl-h-13">
+ {{ $options.I18N.learnBasics.migrateFromJenkins.description }}
+ </p>
+ </div>
+
+ <gl-button
+ category="primary"
+ variant="confirm"
+ :href="migrationPlanUrl"
+ target="_blank"
+ @click="trackEvent('template_clicked', migrationPromptTrackingLabel)"
+ >
+ {{ $options.I18N.learnBasics.migrateFromJenkins.cta }}
+ </gl-button>
+ </gl-card>
+ </div>
+
+ <div class="gl-lg-w-25p gl-md-w-half gl-w-full gl-pb-8">
+ <gl-card>
+ <div class="gl-flex-direction-row">
+ <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div>
+ <div class="gl-mb-3">
+ <strong class="gl-text-gray-800 gl-mb-2">
+ {{ $options.I18N.learnBasics.gettingStarted.title }}
+ </strong>
+ </div>
+ <p class="gl-font-sm gl-h-13">
+ {{ $options.I18N.learnBasics.gettingStarted.description }}
+ </p>
</div>
- <p class="gl-font-sm">{{ $options.I18N.learnBasics.gettingStarted.description }}</p>
- </div>
- <gl-button
- category="primary"
- variant="confirm"
- :href="gettingStartedTemplateUrl"
- data-testid="test-template-link"
- @click="trackEvent($options.STARTER_TEMPLATE_NAME)"
- >
- {{ $options.I18N.learnBasics.gettingStarted.cta }}
- </gl-button>
- </gl-card>
+ <gl-button
+ category="primary"
+ variant="confirm"
+ :href="gettingStartedTemplateUrl"
+ data-testid="test-template-link"
+ @click="trackEvent('template_clicked', $options.STARTER_TEMPLATE_NAME)"
+ >
+ {{ $options.I18N.learnBasics.gettingStarted.cta }}
+ </gl-button>
+ </gl-card>
+ </div>
</div>
<h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.templates.title }}</h2>
diff --git a/app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue b/app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue
index 3021b4a2ef8..634fc2ad447 100644
--- a/app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/ci/pipelines_page/components/pipelines_artifacts.vue
@@ -70,3 +70,11 @@ export default {
:items="items"
/>
</template>
+
+<style scoped>
+/* TODO: Use max-height prop when gitlab-ui got updated.
+See https://gitlab.com/gitlab-org/gitlab-ui/-/issues/2374 */
+::v-deep .gl-new-dropdown-inner {
+ max-height: 310px;
+}
+</style>
diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js
deleted file mode 100644
index 8f3681952c6..00000000000
--- a/app/assets/javascripts/header.js
+++ /dev/null
@@ -1,50 +0,0 @@
-// TODO: Remove this with the removal of the old navigation.
-// See https://gitlab.com/groups/gitlab-org/-/epics/11875.
-
-import { highCountTrim } from '~/lib/utils/text_utility';
-import Tracking from '~/tracking';
-
-/**
- * Updates todo counter when todos are toggled.
- * When count is 0, we hide the badge.
- *
- * @param {jQuery.Event} e
- * @param {String} count
- */
-export default function initTodoToggle() {
- document.addEventListener('todo:toggle', (e) => {
- const updatedCount = e.detail.count || 0;
- const todoPendingCount = document.querySelector('.js-todos-count');
-
- if (todoPendingCount) {
- todoPendingCount.textContent = highCountTrim(updatedCount);
- if (updatedCount === 0) {
- todoPendingCount.classList.add('hidden');
- } else {
- todoPendingCount.classList.remove('hidden');
- }
- }
- });
-}
-
-function trackShowUserDropdownLink(trackEvent, elToTrack, el) {
- const { trackLabel, trackProperty } = elToTrack.dataset;
-
- el.addEventListener('shown.bs.dropdown', () => {
- Tracking.event(document.body.dataset.page, trackEvent, {
- label: trackLabel,
- property: trackProperty,
- });
- });
-}
-
-export function initNavUserDropdownTracking() {
- const el = document.querySelector('.js-nav-user-dropdown');
- const buyEl = document.querySelector('.js-buy-pipeline-minutes-link');
-
- if (el && buyEl) {
- trackShowUserDropdownLink('show_buy_ci_minutes', buyEl, el);
- }
-}
-
-requestIdleCallback(initNavUserDropdownTracking);
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index b41f306eabd..43ae13915ad 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -15,7 +15,6 @@ import * as tooltips from '~/tooltips';
import { initPrefetchLinks } from '~/lib/utils/navigation_utility';
import { logHelloDeferred } from 'jh_else_ce/lib/logger/hello_deferred';
import initAlertHandler from './alert_handler';
-import initTodoToggle from './header';
import initLayoutNav from './layout_nav';
import { handleLocationHash, addSelectOnFocusBehaviour } from './lib/utils/common_utils';
import { localTimeAgo } from './lib/utils/datetime/timeago_utility';
@@ -86,7 +85,6 @@ function deferredInitialisation() {
if (!gon.use_new_navigation) {
initTopNav();
- initTodoToggle();
}
initBreadcrumbs();
initPrefetchLinks('.js-prefetch-document');
diff --git a/app/assets/javascripts/vue_shared/components/list_selector/constants.js b/app/assets/javascripts/vue_shared/components/list_selector/constants.js
index cff9c56a1c0..ad826c6f3e5 100644
--- a/app/assets/javascripts/vue_shared/components/list_selector/constants.js
+++ b/app/assets/javascripts/vue_shared/components/list_selector/constants.js
@@ -1,6 +1,26 @@
import { __ } from '~/locale';
+import UserItem from './user_item.vue';
+import GroupItem from './group_item.vue';
+import DeployKeyItem from './deploy_key_item.vue';
export const CONFIG = {
- users: { title: __('Users'), icon: 'user', filterKey: 'username', showNamespaceDropdown: true },
- groups: { title: __('Groups'), icon: 'group', filterKey: 'name' },
+ users: {
+ title: __('Users'),
+ icon: 'user',
+ filterKey: 'username',
+ showNamespaceDropdown: true,
+ component: UserItem,
+ },
+ groups: {
+ title: __('Groups'),
+ icon: 'group',
+ filterKey: 'name',
+ component: GroupItem,
+ },
+ deployKeys: {
+ title: __('Deploy keys'),
+ icon: 'key',
+ filterKey: 'name',
+ component: DeployKeyItem,
+ },
};
diff --git a/app/assets/javascripts/vue_shared/components/list_selector/deploy_key_item.vue b/app/assets/javascripts/vue_shared/components/list_selector/deploy_key_item.vue
new file mode 100644
index 00000000000..4dbbd44f0b5
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/components/list_selector/deploy_key_item.vue
@@ -0,0 +1,51 @@
+<script>
+import { GlButton, GlIcon } from '@gitlab/ui';
+import { sprintf, __ } from '~/locale';
+
+export default {
+ name: 'DeployKeyItem',
+ components: { GlButton, GlIcon },
+ props: {
+ data: {
+ type: Object,
+ required: true,
+ },
+ canDelete: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ const { title, owner, id } = this.data;
+ return {
+ deleteButtonLabel: sprintf(__('Delete %{name}'), { name: title }),
+ title,
+ owner,
+ id,
+ };
+ },
+};
+</script>
+
+<template>
+ <span
+ class="gl-display-flex gl-align-items-center gl-gap-3"
+ data-testid="deploy-key-wrapper"
+ @click="$emit('select', id)"
+ >
+ <gl-icon name="key" />
+ <span class="gl-display-flex gl-flex-direction-column gl-flex-grow-1">
+ <span class="gl-font-weight-bold">{{ title }}</span>
+ <span class="gl-text-gray-600">@{{ owner }}</span>
+ </span>
+
+ <gl-button
+ v-if="canDelete"
+ icon="remove"
+ :aria-label="deleteButtonLabel"
+ category="tertiary"
+ @click.stop="$emit('delete', id)"
+ />
+ </span>
+</template>
diff --git a/app/assets/javascripts/vue_shared/components/list_selector/index.vue b/app/assets/javascripts/vue_shared/components/list_selector/index.vue
index b8480a0c496..d79a8d6a00c 100644
--- a/app/assets/javascripts/vue_shared/components/list_selector/index.vue
+++ b/app/assets/javascripts/vue_shared/components/list_selector/index.vue
@@ -5,8 +5,6 @@ import { createAlert } from '~/alert';
import { __ } from '~/locale';
import groupsAutocompleteQuery from '~/graphql_shared/queries/groups_autocomplete.query.graphql';
import Api from '~/api';
-import UserItem from './user_item.vue';
-import GroupItem from './group_item.vue';
import { CONFIG } from './constants';
const I18N = {
@@ -25,10 +23,6 @@ export default {
GlCollapsibleListbox,
},
props: {
- title: {
- type: String,
- required: true,
- },
type: {
type: String,
required: true,
@@ -61,12 +55,6 @@ export default {
config() {
return CONFIG[this.type];
},
- isUserVariant() {
- return this.type === 'users';
- },
- component() {
- return this.isUserVariant ? UserItem : GroupItem;
- },
namespaceDropdownText() {
return parseBoolean(this.isProjectNamespace)
? this.$options.i18n.projectGroups
@@ -77,12 +65,14 @@ export default {
async handleSearchInput(search) {
this.$refs.results.open();
+ const searchMethod = {
+ users: this.fetchUsersBySearchTerm,
+ groups: this.fetchGroupsBySearchTerm,
+ deployKeys: this.fetchDeployKeysBySearchTerm,
+ };
+
try {
- if (this.isUserVariant) {
- this.items = await this.fetchUsersBySearchTerm(search);
- } else {
- this.items = await this.fetchGroupsBySearchTerm(search);
- }
+ this.items = await searchMethod[this.type](search);
} catch (e) {
createAlert({
message: this.$options.i18n.apiErrorMessage,
@@ -114,6 +104,10 @@ export default {
})),
);
},
+ fetchDeployKeysBySearchTerm() {
+ // TODO - implement API request (follow-up)
+ // https://gitlab.com/gitlab-org/gitlab/-/issues/432494
+ },
getItemByKey(key) {
return this.items.find((item) => item[this.config.filterKey] === key);
},
@@ -139,7 +133,7 @@ export default {
<gl-card header-class="gl-new-card-header gl-border-none" body-class="gl-card-footer">
<template #header
><strong data-testid="list-selector-title"
- >{{ title }}
+ >{{ config.title }}
<span class="gl-text-gray-700 gl-ml-3"
><gl-icon :name="config.icon" /> {{ selectedItems.length }}</span
></strong
@@ -166,7 +160,7 @@ export default {
</template>
<template #list-item="{ item }">
- <component :is="component" :data="item" @select="handleSelectItem" />
+ <component :is="config.component" :data="item" @select="handleSelectItem" />
</template>
</gl-collapsible-listbox>
@@ -180,7 +174,7 @@ export default {
</div>
<component
- :is="component"
+ :is="config.component"
v-for="(item, index) of selectedItems"
:key="index"
:class="{ 'gl-border-t': index > 0 }"
diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb
index 9c4ceaccff1..156df0c4cc4 100644
--- a/app/helpers/ci/pipelines_helper.rb
+++ b/app/helpers/ci/pipelines_helper.rb
@@ -87,7 +87,8 @@ module Ci
pipeline_editor_path: can?(current_user, :create_pipeline, project) && project_ci_pipeline_editor_path(project),
suggested_ci_templates: suggested_ci_templates.to_json,
full_path: project.full_path,
- visibility_pipeline_id_type: visibility_pipeline_id_type
+ visibility_pipeline_id_type: visibility_pipeline_id_type,
+ show_jenkins_ci_prompt: show_jenkins_ci_prompt(project).to_s
}
end
@@ -104,5 +105,12 @@ module Ci
yield markdown(warning.content)
end
end
+
+ def show_jenkins_ci_prompt(project)
+ return false unless can?(current_user, :create_pipeline, project)
+ return false if project.repository.gitlab_ci_yml.present?
+
+ project.repository.jenkinsfile?
+ end
end
end
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 1731562d158..19dc0e40564 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -124,7 +124,7 @@ class AbuseReport < ApplicationRecord
return screenshot.url unless screenshot.upload
asset_host = ActionController::Base.asset_host || Gitlab.config.gitlab.base_url
- local_path = Gitlab::Routing.url_helpers.abuse_report_upload_path(
+ local_path = Gitlab::Routing.url_helpers.abuse_report_screenshot_path(
filename: screenshot.filename,
id: screenshot.upload.model_id,
model: 'abuse_report',
diff --git a/app/models/ci/catalog/listing.rb b/app/models/ci/catalog/listing.rb
index 93cb4830f23..14d043ad156 100644
--- a/app/models/ci/catalog/listing.rb
+++ b/app/models/ci/catalog/listing.rb
@@ -44,7 +44,8 @@ module Ci
attr_reader :current_user
def all_resources
- Ci::Catalog::Resource.joins(:project).includes(:project)
+ Ci::Catalog::Resource.published
+ .joins(:project).includes(:project)
.merge(Project.public_or_visible_to_user(current_user))
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index f9e680edf37..a6160a4c3b4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -679,6 +679,10 @@ class Repository
end
cache_method :gitlab_ci_yml
+ def jenkinsfile?
+ file_on_head(:jenkinsfile).present?
+ end
+
def xcode_project?
file_on_head(:xcode_config, :tree).present?
end
diff --git a/app/serializers/admin/abuse_report_details_entity.rb b/app/serializers/admin/abuse_report_details_entity.rb
index 77b85f239f7..a654482b989 100644
--- a/app/serializers/admin/abuse_report_details_entity.rb
+++ b/app/serializers/admin/abuse_report_details_entity.rb
@@ -76,5 +76,9 @@ module Admin
expose :report do |report|
ReportedContentEntity.represent(report)
end
+
+ expose :upload_note_attachment_path do |report|
+ upload_path('abuse_report', id: report.id)
+ end
end
end