Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
3dcdea9502
commit
8b0ef13236
|
@ -1,5 +1,9 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
## 13.0.4 (2020-06-03)
|
||||
|
||||
- No changes.
|
||||
|
||||
## 13.0.3 (2020-05-29)
|
||||
|
||||
- No changes.
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 13.0.4 (2020-06-03)
|
||||
|
||||
### Security (1 change)
|
||||
|
||||
- Prevent fetching repository code with unauthorized ci token.
|
||||
|
||||
|
||||
## 13.0.3 (2020-05-29)
|
||||
|
||||
### Fixed (8 changes, 1 of them is from the community)
|
||||
|
|
|
@ -177,7 +177,11 @@ export default {
|
|||
{{ createIssueError }}
|
||||
</gl-alert>
|
||||
<div v-if="loading"><gl-loading-icon size="lg" class="gl-mt-5" /></div>
|
||||
<div v-if="alert" class="alert-management-details gl-relative">
|
||||
<div
|
||||
v-if="alert"
|
||||
class="alert-management-details gl-relative"
|
||||
:class="{ 'pr-8': sidebarCollapsed }"
|
||||
>
|
||||
<div
|
||||
class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid flex-column flex-sm-row"
|
||||
>
|
||||
|
|
|
@ -13,7 +13,7 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
type: Number,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
<script>
|
||||
import { ApolloMutation } from 'vue-apollo';
|
||||
import { GlTooltipDirective, GlIcon, GlLoadingIcon, GlLink } from '@gitlab/ui';
|
||||
import { s__ } from '~/locale';
|
||||
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import allVersionsMixin from '../../mixins/all_versions';
|
||||
import createNoteMutation from '../../graphql/mutations/createNote.mutation.graphql';
|
||||
import toggleResolveDiscussionMutation from '../../graphql/mutations/toggle_resolve_discussion.mutation.graphql';
|
||||
import getDesignQuery from '../../graphql/queries/getDesign.query.graphql';
|
||||
import activeDiscussionQuery from '../../graphql/queries/active_discussion.query.graphql';
|
||||
import DesignNote from './design_note.vue';
|
||||
import DesignReplyForm from './design_reply_form.vue';
|
||||
import { updateStoreAfterAddDiscussionComment } from '../../utils/cache_update';
|
||||
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants';
|
||||
import ToggleRepliesWidget from './toggle_replies_widget.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -16,6 +21,14 @@ export default {
|
|||
DesignNote,
|
||||
ReplyPlaceholder,
|
||||
DesignReplyForm,
|
||||
GlIcon,
|
||||
GlLoadingIcon,
|
||||
GlLink,
|
||||
ToggleRepliesWidget,
|
||||
TimeAgoTooltip,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [allVersionsMixin],
|
||||
props: {
|
||||
|
@ -31,15 +44,15 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
discussionIndex: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
resolvedDiscussionsExpanded: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
activeDiscussion: {
|
||||
|
@ -49,6 +62,7 @@ export default {
|
|||
// We watch any changes to the active discussion from the design pins and scroll to this discussion if it exists
|
||||
// We don't want scrollIntoView to be triggered from the discussion click itself
|
||||
if (
|
||||
this.resolvedDiscussionsExpanded &&
|
||||
discussionId &&
|
||||
data.activeDiscussion.source === ACTIVE_DISCUSSION_SOURCE_TYPES.pin &&
|
||||
discussionId === this.discussion.notes[0].id
|
||||
|
@ -66,6 +80,9 @@ export default {
|
|||
discussionComment: '',
|
||||
isFormRendered: false,
|
||||
activeDiscussion: {},
|
||||
isResolving: false,
|
||||
shouldChangeResolvedStatus: false,
|
||||
areRepliesCollapsed: this.discussion.resolved,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -87,6 +104,29 @@ export default {
|
|||
isDiscussionHighlighted() {
|
||||
return this.discussion.notes[0].id === this.activeDiscussion.id;
|
||||
},
|
||||
resolveCheckboxText() {
|
||||
return this.discussion.resolved
|
||||
? s__('DesignManagement|Unresolve thread')
|
||||
: s__('DesignManagement|Resolve thread');
|
||||
},
|
||||
firstNote() {
|
||||
return this.discussion.notes[0];
|
||||
},
|
||||
discussionReplies() {
|
||||
return this.discussion.notes.slice(1);
|
||||
},
|
||||
areRepliesShown() {
|
||||
return !this.discussion.resolved || !this.areRepliesCollapsed;
|
||||
},
|
||||
resolveIconName() {
|
||||
return this.discussion.resolved ? 'check-circle-filled' : 'check-circle';
|
||||
},
|
||||
isRepliesWidgetVisible() {
|
||||
return this.discussion.resolved && this.discussionReplies.length > 0;
|
||||
},
|
||||
isReplyPlaceholderVisible() {
|
||||
return this.areRepliesShown || !this.discussionReplies.length;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addDiscussionComment(
|
||||
|
@ -106,9 +146,12 @@ export default {
|
|||
onDone() {
|
||||
this.discussionComment = '';
|
||||
this.hideForm();
|
||||
if (this.shouldChangeResolvedStatus) {
|
||||
this.toggleResolvedStatus();
|
||||
}
|
||||
},
|
||||
onError(err) {
|
||||
this.$emit('error', err);
|
||||
onCreateNoteError(err) {
|
||||
this.$emit('createNoteError', err);
|
||||
},
|
||||
hideForm() {
|
||||
this.isFormRendered = false;
|
||||
|
@ -123,6 +166,25 @@ export default {
|
|||
}
|
||||
this.isFormRendered = false;
|
||||
},
|
||||
toggleResolvedStatus() {
|
||||
this.isResolving = true;
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: toggleResolveDiscussionMutation,
|
||||
variables: { id: this.discussion.id, resolve: !this.discussion.resolved },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data.errors?.length > 0) {
|
||||
this.$emit('resolveDiscussionError', data.errors[0]);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('resolveDiscussionError', err);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isResolving = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
createNoteMutation,
|
||||
};
|
||||
|
@ -130,20 +192,69 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="design-discussion-wrapper">
|
||||
<div class="badge badge-pill" type="button">{{ discussionIndex }}</div>
|
||||
<div
|
||||
class="design-discussion bordered-box position-relative"
|
||||
class="badge badge-pill gl-display-flex gl-align-items-center gl-justify-content-center"
|
||||
:class="{ resolved: discussion.resolved }"
|
||||
type="button"
|
||||
>
|
||||
{{ discussion.index }}
|
||||
</div>
|
||||
<ul
|
||||
class="design-discussion bordered-box gl-relative gl-p-0 gl-list-style-none"
|
||||
data-qa-selector="design_discussion_content"
|
||||
>
|
||||
<design-note
|
||||
v-for="note in discussion.notes"
|
||||
:note="firstNote"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:is-resolving="isResolving"
|
||||
:class="{ 'gl-bg-blue-50': isDiscussionHighlighted }"
|
||||
@error="$emit('updateNoteError', $event)"
|
||||
>
|
||||
<template v-if="discussion.resolvable" #resolveDiscussion>
|
||||
<button
|
||||
v-gl-tooltip
|
||||
:class="{ 'is-active': discussion.resolved }"
|
||||
:title="resolveCheckboxText"
|
||||
:aria-label="resolveCheckboxText"
|
||||
type="button"
|
||||
class="line-resolve-btn note-action-button gl-mr-3"
|
||||
data-testid="resolve-button"
|
||||
@click.stop="toggleResolvedStatus"
|
||||
>
|
||||
<gl-icon v-if="!isResolving" :name="resolveIconName" data-testid="resolve-icon" />
|
||||
<gl-loading-icon v-else inline />
|
||||
</button>
|
||||
</template>
|
||||
<template v-if="discussion.resolved" #resolvedStatus>
|
||||
<p class="gl-text-gray-700 gl-font-sm gl-m-0 gl-mt-5" data-testid="resolved-message">
|
||||
{{ __('Resolved by') }}
|
||||
<gl-link
|
||||
class="gl-text-gray-700 gl-text-decoration-none gl-font-sm link-inherit-color"
|
||||
:href="discussion.resolvedBy.webUrl"
|
||||
target="_blank"
|
||||
>{{ discussion.resolvedBy.name }}</gl-link
|
||||
>
|
||||
<time-ago-tooltip :time="discussion.resolvedAt" tooltip-placement="bottom" />
|
||||
</p>
|
||||
</template>
|
||||
</design-note>
|
||||
<toggle-replies-widget
|
||||
v-if="isRepliesWidgetVisible"
|
||||
:collapsed="areRepliesCollapsed"
|
||||
:replies="discussionReplies"
|
||||
@toggle="areRepliesCollapsed = !areRepliesCollapsed"
|
||||
/>
|
||||
<design-note
|
||||
v-for="note in discussionReplies"
|
||||
v-show="areRepliesShown"
|
||||
:key="note.id"
|
||||
:note="note"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:is-resolving="isResolving"
|
||||
:class="{ 'gl-bg-blue-50': isDiscussionHighlighted }"
|
||||
@error="$emit('updateNoteError', $event)"
|
||||
/>
|
||||
<div class="reply-wrapper">
|
||||
<li v-show="isReplyPlaceholderVisible" class="reply-wrapper">
|
||||
<reply-placeholder
|
||||
v-if="!isFormRendered"
|
||||
class="qa-discussion-reply"
|
||||
|
@ -159,7 +270,7 @@ export default {
|
|||
}"
|
||||
:update="addDiscussionComment"
|
||||
@done="onDone"
|
||||
@error="onError"
|
||||
@error="onCreateNoteError"
|
||||
>
|
||||
<design-reply-form
|
||||
v-model="discussionComment"
|
||||
|
@ -168,9 +279,16 @@ export default {
|
|||
@submitForm="mutate"
|
||||
@cancelForm="hideForm"
|
||||
@onBlur="handleReplyFormBlur"
|
||||
/>
|
||||
>
|
||||
<template v-if="discussion.resolvable" #resolveCheckbox>
|
||||
<label data-testid="resolve-checkbox">
|
||||
<input v-model="shouldChangeResolvedStatus" type="checkbox" />
|
||||
{{ resolveCheckboxText }}
|
||||
</label>
|
||||
</template>
|
||||
</design-reply-form>
|
||||
</apollo-mutation>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -54,6 +54,9 @@ export default {
|
|||
body: this.noteText,
|
||||
};
|
||||
},
|
||||
isEditButtonVisible() {
|
||||
return !this.isEditing && this.note.userPermissions.adminNote;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.isNoteLinked) {
|
||||
|
@ -107,23 +110,28 @@ export default {
|
|||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
v-if="!isEditing && note.userPermissions.adminNote"
|
||||
v-gl-tooltip
|
||||
type="button"
|
||||
title="Edit comment"
|
||||
class="note-action-button js-note-edit btn btn-transparent qa-note-edit-button"
|
||||
@click="isEditing = true"
|
||||
>
|
||||
<gl-icon name="pencil" class="link-highlight" />
|
||||
</button>
|
||||
<div class="gl-display-flex">
|
||||
<slot name="resolveDiscussion"></slot>
|
||||
<button
|
||||
v-if="isEditButtonVisible"
|
||||
v-gl-tooltip
|
||||
type="button"
|
||||
:title="__('Edit comment')"
|
||||
class="note-action-button js-note-edit btn btn-transparent qa-note-edit-button"
|
||||
@click="isEditing = true"
|
||||
>
|
||||
<gl-icon name="pencil" class="link-highlight" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="!isEditing"
|
||||
class="note-text js-note-text md"
|
||||
data-qa-selector="note_content"
|
||||
v-html="note.bodyHtml"
|
||||
></div>
|
||||
<template v-if="!isEditing">
|
||||
<div
|
||||
class="note-text js-note-text md"
|
||||
data-qa-selector="note_content"
|
||||
v-html="note.bodyHtml"
|
||||
></div>
|
||||
<slot name="resolvedStatus"></slot>
|
||||
</template>
|
||||
<apollo-mutation
|
||||
v-else
|
||||
#default="{ mutate, loading }"
|
||||
|
|
|
@ -108,7 +108,8 @@ export default {
|
|||
</textarea>
|
||||
</template>
|
||||
</markdown-field>
|
||||
<div class="note-form-actions d-flex justify-content-between">
|
||||
<slot name="resolveCheckbox"></slot>
|
||||
<div class="note-form-actions gl-display-flex gl-justify-content-space-between">
|
||||
<gl-deprecated-button
|
||||
ref="submitButton"
|
||||
:disabled="!hasValue || isSaving"
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<script>
|
||||
import { GlIcon, GlButton, GlLink } from '@gitlab/ui';
|
||||
import { __, n__ } from '~/locale';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
export default {
|
||||
name: 'ToggleNotesWidget',
|
||||
components: {
|
||||
GlIcon,
|
||||
GlButton,
|
||||
GlLink,
|
||||
TimeAgoTooltip,
|
||||
},
|
||||
props: {
|
||||
collapsed: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
replies: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
lastReply() {
|
||||
return this.replies[this.replies.length - 1];
|
||||
},
|
||||
iconName() {
|
||||
return this.collapsed ? 'chevron-right' : 'chevron-down';
|
||||
},
|
||||
toggleText() {
|
||||
return this.collapsed
|
||||
? `${this.replies.length} ${n__('reply', 'replies', this.replies.length)}`
|
||||
: __('Collapse replies');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li
|
||||
class="toggle-comments gl-bg-gray-50 gl-display-flex gl-align-items-center gl-py-3"
|
||||
:class="{ expanded: !collapsed }"
|
||||
data-testid="toggle-comments-wrapper"
|
||||
>
|
||||
<gl-icon :name="iconName" class="gl-ml-3" @click.stop="$emit('toggle')" />
|
||||
<gl-button
|
||||
variant="link"
|
||||
class="toggle-comments-button gl-ml-2 gl-mr-2"
|
||||
@click.stop="$emit('toggle')"
|
||||
>
|
||||
{{ toggleText }}
|
||||
</gl-button>
|
||||
<template v-if="collapsed">
|
||||
<span class="gl-text-gray-700">{{ __('Last reply by') }}</span>
|
||||
<gl-link
|
||||
:href="lastReply.author.webUrl"
|
||||
target="_blank"
|
||||
class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-ml-2 gl-mr-2"
|
||||
>
|
||||
{{ lastReply.author.name }}
|
||||
</gl-link>
|
||||
<time-ago-tooltip
|
||||
:time="lastReply.createdAt"
|
||||
tooltip-placement="bottom"
|
||||
class="gl-text-gray-700"
|
||||
/>
|
||||
</template>
|
||||
</li>
|
||||
</template>
|
|
@ -33,6 +33,10 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
resolvedDiscussionsExpanded: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
apollo: {
|
||||
activeDiscussion: {
|
||||
|
@ -236,6 +240,9 @@ export default {
|
|||
isNoteInactive(note) {
|
||||
return this.activeDiscussion.id && this.activeDiscussion.id !== note.id;
|
||||
},
|
||||
designPinClass(note) {
|
||||
return { inactive: this.isNoteInactive(note), resolved: note.resolved };
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -254,20 +261,23 @@ export default {
|
|||
data-qa-selector="design_image_button"
|
||||
@mouseup="onAddCommentMouseup"
|
||||
></button>
|
||||
<design-note-pin
|
||||
v-for="(note, index) in notes"
|
||||
:key="note.id"
|
||||
:label="`${index + 1}`"
|
||||
:repositioning="isMovingNote(note.id)"
|
||||
:position="
|
||||
isMovingNote(note.id) && movingNoteNewPosition
|
||||
? getNotePositionStyle(movingNoteNewPosition)
|
||||
: getNotePositionStyle(note.position)
|
||||
"
|
||||
:class="{ inactive: isNoteInactive(note) }"
|
||||
@mousedown.stop="onNoteMousedown($event, note)"
|
||||
@mouseup.stop="onNoteMouseup(note)"
|
||||
/>
|
||||
<template v-for="note in notes">
|
||||
<design-note-pin
|
||||
v-if="resolvedDiscussionsExpanded || !note.resolved"
|
||||
:key="note.id"
|
||||
:label="note.index"
|
||||
:repositioning="isMovingNote(note.id)"
|
||||
:position="
|
||||
isMovingNote(note.id) && movingNoteNewPosition
|
||||
? getNotePositionStyle(movingNoteNewPosition)
|
||||
: getNotePositionStyle(note.position)
|
||||
"
|
||||
:class="designPinClass(note)"
|
||||
@mousedown.stop="onNoteMousedown($event, note)"
|
||||
@mouseup.stop="onNoteMouseup(note)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<design-note-pin
|
||||
v-if="currentCommentForm"
|
||||
:position="currentCommentPositionStyle"
|
||||
|
|
|
@ -35,6 +35,10 @@ export default {
|
|||
required: false,
|
||||
default: 1,
|
||||
},
|
||||
resolvedDiscussionsExpanded: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -54,7 +58,10 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
discussionStartingNotes() {
|
||||
return this.discussions.map(discussion => discussion.notes[0]);
|
||||
return this.discussions.map(discussion => ({
|
||||
...discussion.notes[0],
|
||||
index: discussion.index,
|
||||
}));
|
||||
},
|
||||
currentCommentForm() {
|
||||
return (this.isAnnotating && this.currentAnnotationPosition) || null;
|
||||
|
@ -305,6 +312,7 @@ export default {
|
|||
:notes="discussionStartingNotes"
|
||||
:current-comment-form="currentCommentForm"
|
||||
:disable-commenting="isDraggingDesign"
|
||||
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
|
||||
@openCommentForm="openCommentForm"
|
||||
@closeCommentForm="closeCommentForm"
|
||||
@moveNote="moveNote"
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
<script>
|
||||
import { s__ } from '~/locale';
|
||||
import Cookies from 'js-cookie';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import { GlCollapse, GlButton, GlPopover } from '@gitlab/ui';
|
||||
import updateActiveDiscussionMutation from '../graphql/mutations/update_active_discussion.mutation.graphql';
|
||||
import { extractDiscussions, extractParticipants } from '../utils/design_management_utils';
|
||||
import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../constants';
|
||||
import DesignDiscussion from './design_notes/design_discussion.vue';
|
||||
import Participants from '~/sidebar/components/participants/participants.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
DesignDiscussion,
|
||||
Participants,
|
||||
GlCollapse,
|
||||
GlButton,
|
||||
GlPopover,
|
||||
},
|
||||
props: {
|
||||
design: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
resolvedDiscussionsExpanded: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
markdownPreviewPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isResolvedCommentsPopoverHidden: parseBoolean(Cookies.get(this.$options.cookieKey)),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
discussions() {
|
||||
return extractDiscussions(this.design.discussions);
|
||||
},
|
||||
issue() {
|
||||
return {
|
||||
...this.design.issue,
|
||||
webPath: this.design.issue.webPath.substr(1),
|
||||
};
|
||||
},
|
||||
discussionParticipants() {
|
||||
return extractParticipants(this.issue.participants);
|
||||
},
|
||||
resolvedDiscussions() {
|
||||
return this.discussions.filter(discussion => discussion.resolved);
|
||||
},
|
||||
unresolvedDiscussions() {
|
||||
return this.discussions.filter(discussion => !discussion.resolved);
|
||||
},
|
||||
resolvedCommentsToggleIcon() {
|
||||
return this.resolvedDiscussionsExpanded ? 'chevron-down' : 'chevron-right';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSidebarClick() {
|
||||
this.isResolvedCommentsPopoverHidden = true;
|
||||
Cookies.set(this.$options.cookieKey, 'true', { expires: 365 * 10 });
|
||||
this.updateActiveDiscussion();
|
||||
},
|
||||
updateActiveDiscussion(id) {
|
||||
this.$apollo.mutate({
|
||||
mutation: updateActiveDiscussionMutation,
|
||||
variables: {
|
||||
id,
|
||||
source: ACTIVE_DISCUSSION_SOURCE_TYPES.discussion,
|
||||
},
|
||||
});
|
||||
},
|
||||
closeCommentForm() {
|
||||
this.comment = '';
|
||||
this.$emit('closeCommentForm');
|
||||
},
|
||||
},
|
||||
resolveCommentsToggleText: s__('DesignManagement|Resolved Comments'),
|
||||
cookieKey: 'hide_design_resolved_comments_popover',
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="image-notes" @click="handleSidebarClick">
|
||||
<h2 class="gl-font-weight-bold gl-mt-0">
|
||||
{{ issue.title }}
|
||||
</h2>
|
||||
<a
|
||||
class="gl-text-gray-600 gl-text-decoration-none gl-mb-6 gl-display-block"
|
||||
:href="issue.webUrl"
|
||||
>{{ issue.webPath }}</a
|
||||
>
|
||||
<participants
|
||||
:participants="discussionParticipants"
|
||||
:show-participant-label="false"
|
||||
class="gl-mb-4"
|
||||
/>
|
||||
<h2
|
||||
v-if="unresolvedDiscussions.length === 0"
|
||||
class="new-discussion-disclaimer gl-font-base gl-m-0 gl-mb-4"
|
||||
data-testid="new-discussion-disclaimer"
|
||||
>
|
||||
{{ s__("DesignManagement|Click the image where you'd like to start a new discussion") }}
|
||||
</h2>
|
||||
<design-discussion
|
||||
v-for="discussion in unresolvedDiscussions"
|
||||
:key="discussion.id"
|
||||
:discussion="discussion"
|
||||
:design-id="$route.params.id"
|
||||
:noteable-id="design.id"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
|
||||
data-testid="unresolved-discussion"
|
||||
@createNoteError="$emit('onDesignDiscussionError', $event)"
|
||||
@updateNoteError="$emit('updateNoteError', $event)"
|
||||
@resolveDiscussionError="$emit('resolveDiscussionError', $event)"
|
||||
@click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
|
||||
/>
|
||||
<template v-if="resolvedDiscussions.length > 0">
|
||||
<gl-button
|
||||
id="resolved-comments"
|
||||
data-testid="resolved-comments"
|
||||
:icon="resolvedCommentsToggleIcon"
|
||||
variant="link"
|
||||
class="link-inherit-color gl-text-black-normal gl-text-decoration-none gl-font-weight-bold gl-mb-4"
|
||||
@click="$emit('toggleResolvedComments')"
|
||||
>{{ $options.resolveCommentsToggleText }} ({{ resolvedDiscussions.length }})
|
||||
</gl-button>
|
||||
<gl-popover
|
||||
v-if="!isResolvedCommentsPopoverHidden"
|
||||
:show="!isResolvedCommentsPopoverHidden"
|
||||
target="resolved-comments"
|
||||
container="popovercontainer"
|
||||
placement="top"
|
||||
:title="s__('DesignManagement|Resolved Comments')"
|
||||
>
|
||||
<p>
|
||||
{{
|
||||
s__(
|
||||
'DesignManagement|Comments you resolve can be viewed and unresolved by going to the "Resolved Comments" section below',
|
||||
)
|
||||
}}
|
||||
</p>
|
||||
<a href="#" rel="noopener noreferrer" target="_blank">{{
|
||||
s__('DesignManagement|Learn more about resolving comments')
|
||||
}}</a>
|
||||
</gl-popover>
|
||||
<gl-collapse :visible="resolvedDiscussionsExpanded" class="gl-mt-3">
|
||||
<design-discussion
|
||||
v-for="discussion in resolvedDiscussions"
|
||||
:key="discussion.id"
|
||||
:discussion="discussion"
|
||||
:design-id="$route.params.id"
|
||||
:noteable-id="design.id"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
|
||||
data-testid="resolved-discussion"
|
||||
@error="$emit('onDesignDiscussionError', $event)"
|
||||
@updateNoteError="$emit('updateNoteError', $event)"
|
||||
@click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
|
||||
/>
|
||||
</gl-collapse>
|
||||
</template>
|
||||
<slot name="replyForm"></slot>
|
||||
</div>
|
||||
</template>
|
|
@ -1,6 +1,7 @@
|
|||
#import "./designNote.fragment.graphql"
|
||||
#import "./designList.fragment.graphql"
|
||||
#import "./diffRefs.fragment.graphql"
|
||||
#import "./discussion_resolved_status.fragment.graphql"
|
||||
|
||||
fragment DesignItem on Design {
|
||||
...DesignListItem
|
||||
|
@ -12,6 +13,7 @@ fragment DesignItem on Design {
|
|||
nodes {
|
||||
id
|
||||
replyId
|
||||
...ResolvedStatus
|
||||
notes {
|
||||
nodes {
|
||||
...DesignNote
|
||||
|
|
|
@ -10,6 +10,7 @@ fragment DesignNote on Note {
|
|||
body
|
||||
bodyHtml
|
||||
createdAt
|
||||
resolved
|
||||
position {
|
||||
diffRefs {
|
||||
...DesignDiffRefs
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
fragment ResolvedStatus on Discussion {
|
||||
resolvable
|
||||
resolved
|
||||
resolvedAt
|
||||
resolvedBy {
|
||||
name
|
||||
webUrl
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
#import "../fragments/designNote.fragment.graphql"
|
||||
#import "../fragments/discussion_resolved_status.fragment.graphql"
|
||||
|
||||
mutation toggleResolveDiscussion($id: ID!, $resolve: Boolean!) {
|
||||
discussionToggleResolve(input: { id: $id, resolve: $resolve }) {
|
||||
discussion {
|
||||
id
|
||||
...ResolvedStatus
|
||||
notes {
|
||||
nodes {
|
||||
...DesignNote
|
||||
}
|
||||
}
|
||||
}
|
||||
errors
|
||||
}
|
||||
}
|
|
@ -1,17 +1,16 @@
|
|||
<script>
|
||||
import { ApolloMutation } from 'vue-apollo';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import { GlLoadingIcon, GlAlert } from '@gitlab/ui';
|
||||
import { ApolloMutation } from 'vue-apollo';
|
||||
import createFlash from '~/flash';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
import allVersionsMixin from '../../mixins/all_versions';
|
||||
import Toolbar from '../../components/toolbar/index.vue';
|
||||
import DesignDiscussion from '../../components/design_notes/design_discussion.vue';
|
||||
import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
|
||||
import DesignDestroyer from '../../components/design_destroyer.vue';
|
||||
import DesignScaler from '../../components/design_scaler.vue';
|
||||
import Participants from '~/sidebar/components/participants/participants.vue';
|
||||
import DesignPresentation from '../../components/design_presentation.vue';
|
||||
import DesignReplyForm from '../../components/design_notes/design_reply_form.vue';
|
||||
import DesignSidebar from '../../components/design_sidebar.vue';
|
||||
import getDesignQuery from '../../graphql/queries/getDesign.query.graphql';
|
||||
import appDataQuery from '../../graphql/queries/appData.query.graphql';
|
||||
import createImageDiffNoteMutation from '../../graphql/mutations/createImageDiffNote.mutation.graphql';
|
||||
|
@ -20,7 +19,6 @@ import updateActiveDiscussionMutation from '../../graphql/mutations/update_activ
|
|||
import {
|
||||
extractDiscussions,
|
||||
extractDesign,
|
||||
extractParticipants,
|
||||
updateImageDiffNoteOptimisticResponse,
|
||||
} from '../../utils/design_management_utils';
|
||||
import {
|
||||
|
@ -43,15 +41,14 @@ import { ACTIVE_DISCUSSION_SOURCE_TYPES } from '../../constants';
|
|||
export default {
|
||||
components: {
|
||||
ApolloMutation,
|
||||
DesignReplyForm,
|
||||
DesignPresentation,
|
||||
DesignDiscussion,
|
||||
DesignScaler,
|
||||
DesignDestroyer,
|
||||
Toolbar,
|
||||
DesignReplyForm,
|
||||
GlLoadingIcon,
|
||||
GlAlert,
|
||||
Participants,
|
||||
DesignSidebar,
|
||||
},
|
||||
mixins: [allVersionsMixin],
|
||||
props: {
|
||||
|
@ -69,6 +66,7 @@ export default {
|
|||
errorMessage: '',
|
||||
issueIid: '',
|
||||
scale: 1,
|
||||
resolvedDiscussionsExpanded: false,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
@ -103,20 +101,17 @@ export default {
|
|||
return this.$apollo.queries.design.loading && !this.design.filename;
|
||||
},
|
||||
discussions() {
|
||||
if (!this.design.discussions) {
|
||||
return [];
|
||||
}
|
||||
return extractDiscussions(this.design.discussions);
|
||||
},
|
||||
discussionParticipants() {
|
||||
return extractParticipants(this.design.issue.participants);
|
||||
},
|
||||
markdownPreviewPath() {
|
||||
return `/${this.projectPath}/preview_markdown?target_type=Issue`;
|
||||
},
|
||||
isSubmitButtonDisabled() {
|
||||
return this.comment.trim().length === 0;
|
||||
},
|
||||
renderDiscussions() {
|
||||
return this.discussions.length || this.annotationCoordinates;
|
||||
},
|
||||
designVariables() {
|
||||
return {
|
||||
fullPath: this.projectPath,
|
||||
|
@ -144,15 +139,19 @@ export default {
|
|||
},
|
||||
};
|
||||
},
|
||||
issue() {
|
||||
return {
|
||||
...this.design.issue,
|
||||
webPath: this.design.issue.webPath.substr(1),
|
||||
};
|
||||
},
|
||||
isAnnotating() {
|
||||
return Boolean(this.annotationCoordinates);
|
||||
},
|
||||
resolvedDiscussions() {
|
||||
return this.discussions.filter(discussion => discussion.resolved);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
resolvedDiscussions(val) {
|
||||
if (!val.length) {
|
||||
this.resolvedDiscussionsExpanded = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
Mousetrap.bind('esc', this.closeDesign);
|
||||
|
@ -250,6 +249,9 @@ export default {
|
|||
onDesignDeleteError(e) {
|
||||
this.onError(designDeletionError({ singular: true }), e);
|
||||
},
|
||||
onResolveDiscussionError(e) {
|
||||
this.onError(UPDATE_IMAGE_DIFF_NOTE_ERROR, e);
|
||||
},
|
||||
openCommentForm(annotationCoordinates) {
|
||||
this.annotationCoordinates = annotationCoordinates;
|
||||
},
|
||||
|
@ -281,6 +283,9 @@ export default {
|
|||
},
|
||||
});
|
||||
},
|
||||
toggleResolvedComments() {
|
||||
this.resolvedDiscussionsExpanded = !this.resolvedDiscussionsExpanded;
|
||||
},
|
||||
},
|
||||
createImageDiffNoteMutation,
|
||||
DESIGNS_ROUTE_NAME,
|
||||
|
@ -323,6 +328,7 @@ export default {
|
|||
:discussions="discussions"
|
||||
:is-annotating="isAnnotating"
|
||||
:scale="scale"
|
||||
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
|
||||
@openCommentForm="openCommentForm"
|
||||
@closeCommentForm="closeCommentForm"
|
||||
@moveNote="onMoveNote"
|
||||
|
@ -332,33 +338,19 @@ export default {
|
|||
<design-scaler @scale="scale = $event" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="image-notes" @click="updateActiveDiscussion()">
|
||||
<h2 class="gl-font-size-20-deprecated-no-really-do-not-use-me font-weight-bold mt-0">
|
||||
{{ issue.title }}
|
||||
</h2>
|
||||
<a class="text-tertiary text-decoration-none mb-3 d-block" :href="issue.webUrl">{{
|
||||
issue.webPath
|
||||
}}</a>
|
||||
<participants
|
||||
:participants="discussionParticipants"
|
||||
:show-participant-label="false"
|
||||
class="mb-4"
|
||||
/>
|
||||
<template v-if="renderDiscussions">
|
||||
<design-discussion
|
||||
v-for="(discussion, index) in discussions"
|
||||
:key="discussion.id"
|
||||
:discussion="discussion"
|
||||
:design-id="id"
|
||||
:noteable-id="design.id"
|
||||
:discussion-index="index + 1"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
@error="onDesignDiscussionError"
|
||||
@updateNoteError="onUpdateNoteError"
|
||||
@click.native.stop="updateActiveDiscussion(discussion.notes[0].id)"
|
||||
/>
|
||||
<design-sidebar
|
||||
:design="design"
|
||||
:resolved-discussions-expanded="resolvedDiscussionsExpanded"
|
||||
:markdown-preview-path="markdownPreviewPath"
|
||||
@onDesignDiscussionError="onDesignDiscussionError"
|
||||
@onCreateImageDiffNoteError="onCreateImageDiffNoteError"
|
||||
@updateNoteError="onUpdateNoteError"
|
||||
@resolveDiscussionError="onResolveDiscussionError"
|
||||
@toggleResolvedComments="toggleResolvedComments"
|
||||
>
|
||||
<template #replyForm>
|
||||
<apollo-mutation
|
||||
v-if="annotationCoordinates"
|
||||
v-if="isAnnotating"
|
||||
#default="{ mutate, loading }"
|
||||
:mutation="$options.createImageDiffNoteMutation"
|
||||
:variables="{
|
||||
|
@ -374,13 +366,9 @@ export default {
|
|||
:markdown-preview-path="markdownPreviewPath"
|
||||
@submitForm="mutate"
|
||||
@cancelForm="closeCommentForm"
|
||||
/>
|
||||
</apollo-mutation>
|
||||
</template>
|
||||
<h2 v-else class="new-discussion-disclaimer gl-font-base m-0">
|
||||
{{ __("Click the image where you'd like to start a new discussion") }}
|
||||
</h2>
|
||||
</div>
|
||||
/> </apollo-mutation
|
||||
></template>
|
||||
</design-sidebar>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -95,6 +95,10 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) =
|
|||
__typename: 'Discussion',
|
||||
id: createImageDiffNote.note.discussion.id,
|
||||
replyId: createImageDiffNote.note.discussion.replyId,
|
||||
resolvable: true,
|
||||
resolved: false,
|
||||
resolvedAt: null,
|
||||
resolvedBy: null,
|
||||
notes: {
|
||||
__typename: 'NoteConnection',
|
||||
nodes: [createImageDiffNote.note],
|
||||
|
|
|
@ -21,8 +21,9 @@ export const extractNodes = elements => elements.edges.map(({ node }) => node);
|
|||
*/
|
||||
|
||||
export const extractDiscussions = discussions =>
|
||||
discussions.nodes.map(discussion => ({
|
||||
discussions.nodes.map((discussion, index) => ({
|
||||
...discussion,
|
||||
index: index + 1,
|
||||
notes: discussion.notes.nodes,
|
||||
}));
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
COPY_BUILD_TITLE,
|
||||
PUSH_COMMAND_LABEL,
|
||||
COPY_PUSH_TITLE,
|
||||
} from '../constants';
|
||||
} from '../constants/index';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
LIST_DELETE_BUTTON_DISABLED,
|
||||
REMOVE_REPOSITORY_LABEL,
|
||||
ROW_SCHEDULED_FOR_DELETION,
|
||||
} from '../constants';
|
||||
} from '../constants/index';
|
||||
|
||||
export default {
|
||||
name: 'ImageListrow',
|
||||
|
|
|
@ -3,7 +3,12 @@ import { GlEmptyState, GlSprintf, GlLink } from '@gitlab/ui';
|
|||
import { mapState, mapGetters } from 'vuex';
|
||||
import { s__ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import { COPY_LOGIN_TITLE, COPY_BUILD_TITLE, COPY_PUSH_TITLE, QUICK_START } from '../constants';
|
||||
import {
|
||||
COPY_LOGIN_TITLE,
|
||||
COPY_BUILD_TITLE,
|
||||
COPY_PUSH_TITLE,
|
||||
QUICK_START,
|
||||
} from '../constants/index';
|
||||
|
||||
export default {
|
||||
name: 'ProjectEmptyState',
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
EXPIRATION_POLICY_WILL_RUN_IN,
|
||||
EXPIRATION_POLICY_DISABLED_TEXT,
|
||||
EXPIRATION_POLICY_DISABLED_MESSAGE,
|
||||
} from '../constants';
|
||||
} from '../constants/index';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
// List page
|
||||
|
||||
export const CONTAINER_REGISTRY_TITLE = s__('ContainerRegistry|Container Registry');
|
||||
export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection error');
|
||||
export const CONNECTION_ERROR_MESSAGE = s__(
|
||||
`ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}`,
|
||||
);
|
||||
export const LIST_INTRO_TEXT = s__(
|
||||
`ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`,
|
||||
);
|
||||
|
||||
export const LIST_DELETE_BUTTON_DISABLED = s__(
|
||||
'ContainerRegistry|Missing or insufficient permission, delete button disabled',
|
||||
);
|
||||
export const REMOVE_REPOSITORY_LABEL = s__('ContainerRegistry|Remove repository');
|
||||
export const REMOVE_REPOSITORY_MODAL_TEXT = s__(
|
||||
'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
|
||||
);
|
||||
export const ROW_SCHEDULED_FOR_DELETION = s__(
|
||||
`ContainerRegistry|This image repository is scheduled for deletion`,
|
||||
);
|
||||
export const FETCH_IMAGES_LIST_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while fetching the repository list.',
|
||||
);
|
||||
export const FETCH_TAGS_LIST_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while fetching the tags list.',
|
||||
);
|
||||
export const DELETE_IMAGE_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again.',
|
||||
);
|
||||
export const ASYNC_DELETE_IMAGE_ERROR_MESSAGE = s__(
|
||||
`ContainerRegistry|There was an error during the deletion of this image repository, please try again.`,
|
||||
);
|
||||
export const DELETE_IMAGE_SUCCESS_MESSAGE = s__(
|
||||
'ContainerRegistry|%{title} was successfully scheduled for deletion',
|
||||
);
|
||||
|
||||
export const IMAGE_REPOSITORY_LIST_LABEL = s__('ContainerRegistry|Image Repositories');
|
||||
|
||||
export const SEARCH_PLACEHOLDER_TEXT = s__('ContainerRegistry|Filter by name');
|
||||
|
||||
export const EMPTY_RESULT_TITLE = s__('ContainerRegistry|Sorry, your filter produced no results.');
|
||||
export const EMPTY_RESULT_MESSAGE = s__(
|
||||
'ContainerRegistry|To widen your search, change or remove the filters above.',
|
||||
);
|
||||
|
||||
// Image details page
|
||||
|
||||
export const DETAILS_PAGE_TITLE = s__('ContainerRegistry|%{imageName} tags');
|
||||
|
||||
export const DELETE_TAG_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while marking the tag for deletion.',
|
||||
);
|
||||
export const DELETE_TAG_SUCCESS_MESSAGE = s__(
|
||||
'ContainerRegistry|Tag successfully marked for deletion.',
|
||||
);
|
||||
export const DELETE_TAGS_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while marking the tags for deletion.',
|
||||
);
|
||||
export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
|
||||
'ContainerRegistry|Tags successfully marked for deletion.',
|
||||
);
|
||||
|
||||
export const DEFAULT_PAGE = 1;
|
||||
export const DEFAULT_PAGE_SIZE = 10;
|
||||
|
||||
export const GROUP_PAGE_TYPE = 'groups';
|
||||
|
||||
export const LIST_KEY_TAG = 'name';
|
||||
export const LIST_KEY_IMAGE_ID = 'short_revision';
|
||||
export const LIST_KEY_SIZE = 'total_size';
|
||||
export const LIST_KEY_LAST_UPDATED = 'created_at';
|
||||
export const LIST_KEY_ACTIONS = 'actions';
|
||||
export const LIST_KEY_CHECKBOX = 'checkbox';
|
||||
|
||||
export const LIST_LABEL_TAG = s__('ContainerRegistry|Tag');
|
||||
export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID');
|
||||
export const LIST_LABEL_SIZE = s__('ContainerRegistry|Compressed Size');
|
||||
export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated');
|
||||
|
||||
export const REMOVE_TAG_BUTTON_TITLE = s__('ContainerRegistry|Remove tag');
|
||||
export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Remove selected tags');
|
||||
|
||||
export const REMOVE_TAG_CONFIRMATION_TEXT = s__(
|
||||
`ContainerRegistry|You are about to remove %{item}. Are you sure?`,
|
||||
);
|
||||
export const REMOVE_TAGS_CONFIRMATION_TEXT = s__(
|
||||
`ContainerRegistry|You are about to remove %{item} tags. Are you sure?`,
|
||||
);
|
||||
|
||||
export const EMPTY_IMAGE_REPOSITORY_TITLE = s__('ContainerRegistry|This image has no active tags');
|
||||
export const EMPTY_IMAGE_REPOSITORY_MESSAGE = s__(
|
||||
`ContainerRegistry|The last tag related to this image was recently removed.
|
||||
This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
|
||||
If you have any questions, contact your administrator.`,
|
||||
);
|
||||
|
||||
export const ADMIN_GARBAGE_COLLECTION_TIP = s__(
|
||||
'ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
|
||||
);
|
||||
|
||||
// Expiration policies
|
||||
|
||||
export const EXPIRATION_POLICY_WILL_RUN_IN = s__(
|
||||
'ContainerRegistry|Expiration policy will run in %{time}',
|
||||
);
|
||||
|
||||
export const EXPIRATION_POLICY_DISABLED_TEXT = s__(
|
||||
'ContainerRegistry|Expiration policy is disabled',
|
||||
);
|
||||
|
||||
export const EXPIRATION_POLICY_DISABLED_MESSAGE = s__(
|
||||
'ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}',
|
||||
);
|
||||
|
||||
// Quick Start
|
||||
|
||||
export const QUICK_START = s__('ContainerRegistry|CLI Commands');
|
||||
export const LOGIN_COMMAND_LABEL = s__('ContainerRegistry|Login');
|
||||
export const COPY_LOGIN_TITLE = s__('ContainerRegistry|Copy login command');
|
||||
export const BUILD_COMMAND_LABEL = s__('ContainerRegistry|Build an image');
|
||||
export const COPY_BUILD_TITLE = s__('ContainerRegistry|Copy build command');
|
||||
export const PUSH_COMMAND_LABEL = s__('ContainerRegistry|Push an image');
|
||||
export const COPY_PUSH_TITLE = s__('ContainerRegistry|Copy push command');
|
||||
|
||||
// Image state
|
||||
|
||||
export const IMAGE_DELETE_SCHEDULED_STATUS = 'delete_scheduled';
|
||||
export const IMAGE_FAILED_DELETED_STATUS = 'delete_failed';
|
|
@ -0,0 +1,49 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
// Translations strings
|
||||
export const DETAILS_PAGE_TITLE = s__('ContainerRegistry|%{imageName} tags');
|
||||
export const DELETE_TAG_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while marking the tag for deletion.',
|
||||
);
|
||||
export const DELETE_TAG_SUCCESS_MESSAGE = s__(
|
||||
'ContainerRegistry|Tag successfully marked for deletion.',
|
||||
);
|
||||
export const DELETE_TAGS_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while marking the tags for deletion.',
|
||||
);
|
||||
export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
|
||||
'ContainerRegistry|Tags successfully marked for deletion.',
|
||||
);
|
||||
export const LIST_LABEL_TAG = s__('ContainerRegistry|Tag');
|
||||
export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID');
|
||||
export const LIST_LABEL_SIZE = s__('ContainerRegistry|Compressed Size');
|
||||
export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated');
|
||||
export const REMOVE_TAG_BUTTON_TITLE = s__('ContainerRegistry|Remove tag');
|
||||
export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Remove selected tags');
|
||||
export const REMOVE_TAG_CONFIRMATION_TEXT = s__(
|
||||
`ContainerRegistry|You are about to remove %{item}. Are you sure?`,
|
||||
);
|
||||
export const REMOVE_TAGS_CONFIRMATION_TEXT = s__(
|
||||
`ContainerRegistry|You are about to remove %{item} tags. Are you sure?`,
|
||||
);
|
||||
export const EMPTY_IMAGE_REPOSITORY_TITLE = s__('ContainerRegistry|This image has no active tags');
|
||||
export const EMPTY_IMAGE_REPOSITORY_MESSAGE = s__(
|
||||
`ContainerRegistry|The last tag related to this image was recently removed.
|
||||
This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
|
||||
If you have any questions, contact your administrator.`,
|
||||
);
|
||||
export const ADMIN_GARBAGE_COLLECTION_TIP = s__(
|
||||
'ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
|
||||
);
|
||||
|
||||
// Parameters
|
||||
|
||||
export const DEFAULT_PAGE = 1;
|
||||
export const DEFAULT_PAGE_SIZE = 10;
|
||||
export const GROUP_PAGE_TYPE = 'groups';
|
||||
export const LIST_KEY_TAG = 'name';
|
||||
export const LIST_KEY_IMAGE_ID = 'short_revision';
|
||||
export const LIST_KEY_SIZE = 'total_size';
|
||||
export const LIST_KEY_LAST_UPDATED = 'created_at';
|
||||
export const LIST_KEY_ACTIONS = 'actions';
|
||||
export const LIST_KEY_CHECKBOX = 'checkbox';
|
|
@ -0,0 +1,11 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
export const EXPIRATION_POLICY_WILL_RUN_IN = s__(
|
||||
'ContainerRegistry|Expiration policy will run in %{time}',
|
||||
);
|
||||
export const EXPIRATION_POLICY_DISABLED_TEXT = s__(
|
||||
'ContainerRegistry|Expiration policy is disabled',
|
||||
);
|
||||
export const EXPIRATION_POLICY_DISABLED_MESSAGE = s__(
|
||||
'ContainerRegistry|Expiration policies help manage the storage space used by the Container Registry, but the expiration policies for this registry are disabled. Contact your administrator to enable. %{docLinkStart}More information%{docLinkEnd}',
|
||||
);
|
|
@ -0,0 +1,4 @@
|
|||
export * from './expiration_policies';
|
||||
export * from './quick_start';
|
||||
export * from './list';
|
||||
export * from './details';
|
|
@ -0,0 +1,48 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
// Translations strings
|
||||
|
||||
export const CONTAINER_REGISTRY_TITLE = s__('ContainerRegistry|Container Registry');
|
||||
export const CONNECTION_ERROR_TITLE = s__('ContainerRegistry|Docker connection error');
|
||||
export const CONNECTION_ERROR_MESSAGE = s__(
|
||||
`ContainerRegistry|We are having trouble connecting to the Registry, which could be due to an issue with your project name or path. %{docLinkStart}More information%{docLinkEnd}`,
|
||||
);
|
||||
export const LIST_INTRO_TEXT = s__(
|
||||
`ContainerRegistry|With the GitLab Container Registry, every project can have its own space to store images. %{docLinkStart}More information%{docLinkEnd}`,
|
||||
);
|
||||
export const LIST_DELETE_BUTTON_DISABLED = s__(
|
||||
'ContainerRegistry|Missing or insufficient permission, delete button disabled',
|
||||
);
|
||||
export const REMOVE_REPOSITORY_LABEL = s__('ContainerRegistry|Remove repository');
|
||||
export const REMOVE_REPOSITORY_MODAL_TEXT = s__(
|
||||
'ContainerRegistry|You are about to remove repository %{title}. Once you confirm, this repository will be permanently deleted.',
|
||||
);
|
||||
export const ROW_SCHEDULED_FOR_DELETION = s__(
|
||||
`ContainerRegistry|This image repository is scheduled for deletion`,
|
||||
);
|
||||
export const FETCH_IMAGES_LIST_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while fetching the repository list.',
|
||||
);
|
||||
export const FETCH_TAGS_LIST_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while fetching the tags list.',
|
||||
);
|
||||
export const DELETE_IMAGE_ERROR_MESSAGE = s__(
|
||||
'ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again.',
|
||||
);
|
||||
export const ASYNC_DELETE_IMAGE_ERROR_MESSAGE = s__(
|
||||
`ContainerRegistry|There was an error during the deletion of this image repository, please try again.`,
|
||||
);
|
||||
export const DELETE_IMAGE_SUCCESS_MESSAGE = s__(
|
||||
'ContainerRegistry|%{title} was successfully scheduled for deletion',
|
||||
);
|
||||
export const IMAGE_REPOSITORY_LIST_LABEL = s__('ContainerRegistry|Image Repositories');
|
||||
export const SEARCH_PLACEHOLDER_TEXT = s__('ContainerRegistry|Filter by name');
|
||||
export const EMPTY_RESULT_TITLE = s__('ContainerRegistry|Sorry, your filter produced no results.');
|
||||
export const EMPTY_RESULT_MESSAGE = s__(
|
||||
'ContainerRegistry|To widen your search, change or remove the filters above.',
|
||||
);
|
||||
|
||||
// Parameters
|
||||
|
||||
export const IMAGE_DELETE_SCHEDULED_STATUS = 'delete_scheduled';
|
||||
export const IMAGE_FAILED_DELETED_STATUS = 'delete_failed';
|
|
@ -0,0 +1,9 @@
|
|||
import { s__ } from '~/locale';
|
||||
|
||||
export const QUICK_START = s__('ContainerRegistry|CLI Commands');
|
||||
export const LOGIN_COMMAND_LABEL = s__('ContainerRegistry|Login');
|
||||
export const COPY_LOGIN_TITLE = s__('ContainerRegistry|Copy login command');
|
||||
export const BUILD_COMMAND_LABEL = s__('ContainerRegistry|Build an image');
|
||||
export const COPY_BUILD_TITLE = s__('ContainerRegistry|Copy build command');
|
||||
export const PUSH_COMMAND_LABEL = s__('ContainerRegistry|Push an image');
|
||||
export const COPY_PUSH_TITLE = s__('ContainerRegistry|Copy push command');
|
|
@ -45,7 +45,7 @@ import {
|
|||
EMPTY_IMAGE_REPOSITORY_TITLE,
|
||||
EMPTY_IMAGE_REPOSITORY_MESSAGE,
|
||||
ADMIN_GARBAGE_COLLECTION_TIP,
|
||||
} from '../constants';
|
||||
} from '../constants/index';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
|
|
@ -29,7 +29,7 @@ import {
|
|||
IMAGE_REPOSITORY_LIST_LABEL,
|
||||
EMPTY_RESULT_TITLE,
|
||||
EMPTY_RESULT_MESSAGE,
|
||||
} from '../constants';
|
||||
} from '../constants/index';
|
||||
|
||||
export default {
|
||||
name: 'RegistryListApp',
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Vue from 'vue';
|
||||
import VueRouter from 'vue-router';
|
||||
import { s__ } from '~/locale';
|
||||
import List from './pages/list.vue';
|
||||
import Details from './pages/details.vue';
|
||||
import { decodeAndParse } from './utils';
|
||||
import { CONTAINER_REGISTRY_TITLE } from './constants/index';
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
|
@ -17,7 +17,7 @@ export default function createRouter(base) {
|
|||
path: '/',
|
||||
component: List,
|
||||
meta: {
|
||||
nameGenerator: () => s__('ContainerRegistry|Container Registry'),
|
||||
nameGenerator: () => CONTAINER_REGISTRY_TITLE,
|
||||
root: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
DEFAULT_PAGE,
|
||||
DEFAULT_PAGE_SIZE,
|
||||
FETCH_TAGS_LIST_ERROR_MESSAGE,
|
||||
} from '../constants';
|
||||
} from '../constants/index';
|
||||
import { decodeAndParse } from '../utils';
|
||||
|
||||
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import * as types from './mutation_types';
|
||||
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
|
||||
import { IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_FAILED_DELETED_STATUS } from '../constants';
|
||||
import { IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_FAILED_DELETED_STATUS } from '../constants/index';
|
||||
|
||||
export default {
|
||||
[types.SET_INITIAL_STATE](state, config) {
|
||||
|
|
|
@ -20,6 +20,20 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.badge.badge-pill {
|
||||
display: flex;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
background-color: $blue-400;
|
||||
color: $white;
|
||||
border: $white 1px solid;
|
||||
border-radius: 50%;
|
||||
|
||||
&.resolved {
|
||||
background-color: $gray-700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.design-presentation-wrapper {
|
||||
|
@ -52,14 +66,31 @@
|
|||
min-width: 400px;
|
||||
flex-basis: 28%;
|
||||
|
||||
.link-inherit-color {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-comments {
|
||||
line-height: 20px;
|
||||
border-top: 1px solid $border-color;
|
||||
|
||||
&.expanded {
|
||||
border-bottom: 1px solid $border-color;
|
||||
}
|
||||
|
||||
.toggle-comments-button:focus {
|
||||
text-decoration: none;
|
||||
color: $blue-600;
|
||||
}
|
||||
}
|
||||
|
||||
.badge.badge-pill {
|
||||
margin-left: $gl-padding;
|
||||
background-color: $blue-400;
|
||||
color: $white;
|
||||
border: $white 1px solid;
|
||||
min-height: 28px;
|
||||
padding: 7px 10px;
|
||||
border-radius: $gl-padding;
|
||||
}
|
||||
|
||||
.design-discussion {
|
||||
|
|
|
@ -9,40 +9,6 @@ module IssuesHelper
|
|||
classes.join(' ')
|
||||
end
|
||||
|
||||
def url_for_issue(issue_iid, project = @project, options = {})
|
||||
return '' if project.nil?
|
||||
|
||||
url =
|
||||
if options[:internal]
|
||||
url_for_internal_issue(issue_iid, project, options)
|
||||
else
|
||||
url_for_tracker_issue(issue_iid, project, options)
|
||||
end
|
||||
|
||||
# Ensure we return a valid URL to prevent possible XSS.
|
||||
URI.parse(url).to_s
|
||||
rescue URI::InvalidURIError
|
||||
''
|
||||
end
|
||||
|
||||
def url_for_tracker_issue(issue_iid, project, options)
|
||||
if options[:only_path]
|
||||
project.issues_tracker.issue_path(issue_iid)
|
||||
else
|
||||
project.issues_tracker.issue_url(issue_iid)
|
||||
end
|
||||
end
|
||||
|
||||
def url_for_internal_issue(issue_iid, project = @project, options = {})
|
||||
helpers = Gitlab::Routing.url_helpers
|
||||
|
||||
if options[:only_path]
|
||||
helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue_iid)
|
||||
else
|
||||
helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue_iid)
|
||||
end
|
||||
end
|
||||
|
||||
def status_box_class(item)
|
||||
if item.try(:expired?)
|
||||
'status-box-expired'
|
||||
|
@ -168,11 +134,6 @@ module IssuesHelper
|
|||
def show_moved_service_desk_issue_warning?(issue)
|
||||
false
|
||||
end
|
||||
|
||||
# Required for Banzai::Filter::IssueReferenceFilter
|
||||
module_function :url_for_issue
|
||||
module_function :url_for_internal_issue
|
||||
module_function :url_for_tracker_issue
|
||||
end
|
||||
|
||||
IssuesHelper.prepend_if_ee('EE::IssuesHelper')
|
||||
|
|
|
@ -450,6 +450,7 @@ class ProjectPolicy < BasePolicy
|
|||
rule { repository_disabled }.policy do
|
||||
prevent :push_code
|
||||
prevent :download_code
|
||||
prevent :build_download_code
|
||||
prevent :fork_project
|
||||
prevent :read_commit_status
|
||||
prevent :read_pipeline
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: "[Frontend] Resolvable design discussions"
|
||||
merge_request: 32399
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix sidebar spacing for alert details
|
||||
merge_request: 33630
|
||||
author:
|
||||
type: fixed
|
|
@ -255,7 +255,7 @@ module.exports = {
|
|||
moduleIds: 'hashed',
|
||||
runtimeChunk: 'single',
|
||||
splitChunks: {
|
||||
maxInitialRequests: 4,
|
||||
maxInitialRequests: 20,
|
||||
cacheGroups: {
|
||||
default: false,
|
||||
common: () => ({
|
||||
|
|
|
@ -16,6 +16,7 @@ exceptions:
|
|||
- AWS
|
||||
- CNAME
|
||||
- CSS
|
||||
- CSV
|
||||
- DNS
|
||||
- GET
|
||||
- GNU
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
---
|
||||
extends: existence
|
||||
message: "Remove profanity: '%s'"
|
||||
ignorecase: true
|
||||
level: error
|
||||
tokens:
|
||||
- 'arse(hole)?'
|
||||
- 'ass(hole)?'
|
||||
- 'bastard'
|
||||
- 'bitch'
|
||||
- 'bloody'
|
||||
- 'bollocks'
|
||||
- 'bugger'
|
||||
- 'cocksucker'
|
||||
- 'crap'
|
||||
- 'cunt'
|
||||
- 'damn'
|
||||
- 'eff(ing)?'
|
||||
- 'fart'
|
||||
- 'fuck(er|ing)?'
|
||||
- 'goddamn(it?|ed)'
|
||||
- 'hell'
|
||||
- 'horseshit'
|
||||
- 'motherfuck(ers?|ing)'
|
||||
- 'piss(ing)?'
|
||||
- 'shit'
|
||||
- 'tits'
|
||||
- 'turd'
|
||||
- 'twat'
|
||||
- 'wank(er|ing)?'
|
|
@ -8,8 +8,8 @@ For a full list of reference architectures, see
|
|||
> - **High Availability:** True
|
||||
> - **Test RPS rates:** API: 200 RPS, Web: 20 RPS, Git: 20 RPS
|
||||
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS ([9](#footnotes)) | Azure([9](#footnotes)) |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|----------------|-----------------------|------------------------|
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|----------------|-----------------------|----------------|
|
||||
| GitLab Rails ([1](#footnotes)) | 3 | 32 vCPU, 28.8GB Memory | n1-highcpu-32 | c5.9xlarge | F32s v2 |
|
||||
| PostgreSQL | 3 | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge | D4s v3 |
|
||||
| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large | F2s v2 |
|
||||
|
@ -74,6 +74,3 @@ For a full list of reference architectures, see
|
|||
or higher, are required for your CPU or Node counts accordingly. For more information, a
|
||||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
|
|
@ -7,7 +7,7 @@ For a full list of reference architectures, see
|
|||
> - **Supported users (approximate):** 1,000
|
||||
> - **High Availability:** False
|
||||
|
||||
| Users | Configuration([8](#footnotes)) | GCP | AWS([9](#footnotes)) | Azure([9](#footnotes)) |
|
||||
| Users | Configuration([8](#footnotes)) | GCP | AWS | Azure |
|
||||
|-------|--------------------------------|---------------|----------------------|------------------------|
|
||||
| 100 | 2 vCPU, 7.2GB Memory | n1-standard-2 | m5.large | D2s v3 |
|
||||
| 500 | 4 vCPU, 15GB Memory | n1-standard-4 | m5.xlarge | D4s v3 |
|
||||
|
@ -77,6 +77,3 @@ added performance and reliability at a reduced complexity cost.
|
|||
or higher, are required for your CPU or Node counts accordingly. For more information, a
|
||||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
|
|
@ -8,8 +8,8 @@ For a full list of reference architectures, see
|
|||
> - **High Availability:** True
|
||||
> - **Test RPS rates:** API: 500 RPS, Web: 50 RPS, Git: 50 RPS
|
||||
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS ([9](#footnotes)) | Azure([9](#footnotes)) |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|----------------|-----------------------|------------------------|
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|----------------|-----------------------|----------------|
|
||||
| GitLab Rails ([1](#footnotes)) | 5 | 32 vCPU, 28.8GB Memory | n1-highcpu-32 | c5.9xlarge | F32s v2 |
|
||||
| PostgreSQL | 3 | 8 vCPU, 30GB Memory | n1-standard-8 | m5.2xlarge | D8s v3 |
|
||||
| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large | F2s v2 |
|
||||
|
@ -74,6 +74,3 @@ For a full list of reference architectures, see
|
|||
or higher, are required for your CPU or Node counts accordingly. For more information, a
|
||||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
|
|
@ -8,7 +8,7 @@ For a full list of reference architectures, see
|
|||
> - **High Availability:** False
|
||||
> - **Test RPS rates:** API: 40 RPS, Web: 4 RPS, Git: 4 RPS
|
||||
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS ([9](#footnotes)) | Azure([9](#footnotes)) |
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|---------------|-----------------------|----------------|
|
||||
| External load balancing node ([6](#footnotes)) | 1 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large | F2s v2 |
|
||||
| Object Storage ([4](#footnotes)) | - | - | - | - | - |
|
||||
|
@ -85,6 +85,3 @@ For a full list of reference architectures, see
|
|||
or higher, are required for your CPU or Node counts accordingly. For more information, a
|
||||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
|
|
@ -14,8 +14,8 @@ following the [2,000-user reference architecture](2k_users.md).
|
|||
> - **High Availability:** True
|
||||
> - **Test RPS rates:** API: 60 RPS, Web: 6 RPS, Git: 6 RPS
|
||||
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS ([9](#footnotes)) | Azure([9](#footnotes)) |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|---------------|-----------------------|------------------------|
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|---------------|-----------------------|----------------|
|
||||
| GitLab Rails ([1](#footnotes)) | 3 | 8 vCPU, 7.2GB Memory | n1-highcpu-8 | c5.2xlarge | F8s v2 |
|
||||
| PostgreSQL | 3 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large | D2s v3 |
|
||||
| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large | F2s v2 |
|
||||
|
@ -77,6 +77,3 @@ following the [2,000-user reference architecture](2k_users.md).
|
|||
or higher, are required for your CPU or Node counts accordingly. For more information, a
|
||||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
|
|
@ -8,8 +8,8 @@ For a full list of reference architectures, see
|
|||
> - **High Availability:** True
|
||||
> - **Test RPS rates:** API: 1000 RPS, Web: 100 RPS, Git: 100 RPS
|
||||
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS ([9](#footnotes)) | Azure([9](#footnotes)) |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|----------------|-----------------------|------------------------|
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|----------------|-----------------------|----------------|
|
||||
| GitLab Rails ([1](#footnotes)) | 12 | 32 vCPU, 28.8GB Memory | n1-highcpu-32 | c5.9xlarge | F32s v2 |
|
||||
| PostgreSQL | 3 | 16 vCPU, 60GB Memory | n1-standard-16 | m5.4xlarge | D16s v3 |
|
||||
| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large | F2s v2 |
|
||||
|
@ -74,6 +74,3 @@ For a full list of reference architectures, see
|
|||
or higher, are required for your CPU or Node counts accordingly. For more information, a
|
||||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
|
|
@ -8,8 +8,8 @@ For a full list of reference architectures, see
|
|||
> - **High Availability:** True
|
||||
> - **Test RPS rates:** API: 100 RPS, Web: 10 RPS, Git: 10 RPS
|
||||
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS ([9](#footnotes)) | Azure([9](#footnotes)) |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|---------------|-----------------------|------------------------|
|
||||
| Service | Nodes | Configuration ([8](#footnotes)) | GCP | AWS | Azure |
|
||||
|--------------------------------------------------------------|-------|---------------------------------|---------------|-----------------------|----------------|
|
||||
| GitLab Rails ([1](#footnotes)) | 3 | 16 vCPU, 14.4GB Memory | n1-highcpu-16 | c5.4xlarge | F16s v2 |
|
||||
| PostgreSQL | 3 | 2 vCPU, 7.5GB Memory | n1-standard-2 | m5.large | D2s v3 |
|
||||
| PgBouncer | 3 | 2 vCPU, 1.8GB Memory | n1-highcpu-2 | c5.large | F2s v2 |
|
||||
|
@ -71,6 +71,3 @@ For a full list of reference architectures, see
|
|||
or higher, are required for your CPU or Node counts accordingly. For more information, a
|
||||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
|
|
@ -163,7 +163,7 @@ column.
|
|||
| Repmgr | PostgreSQL cluster management and failover | [PostgreSQL and Repmgr configuration](../high_availability/database.md) | Yes |
|
||||
| [Redis](../../development/architecture.md#redis) ([3](#footnotes)) | Key/value store for fast data lookup and caching | [Redis configuration](../high_availability/redis.md) | Yes |
|
||||
| Redis Sentinel | Redis | [Redis Sentinel configuration](../high_availability/redis.md) | Yes |
|
||||
| [Gitaly](../../development/architecture.md#gitaly) ([2](#footnotes)) ([7](#footnotes)) ([10](#footnotes)) | Provides access to Git repositories | [Gitaly configuration](../gitaly/index.md#running-gitaly-on-its-own-server) | Yes |
|
||||
| [Gitaly](../../development/architecture.md#gitaly) ([2](#footnotes)) ([7](#footnotes)) ([9](#footnotes)) | Provides access to Git repositories | [Gitaly configuration](../gitaly/index.md#running-gitaly-on-its-own-server) | Yes |
|
||||
| [Sidekiq](../../development/architecture.md#sidekiq) | Asynchronous/background jobs | [Sidekiq configuration](../high_availability/sidekiq.md) | Yes |
|
||||
| [GitLab application services](../../development/architecture.md#unicorn)([1](#footnotes)) | Puma/Unicorn, Workhorse, GitLab Shell - serves front-end requests (UI, API, Git over HTTP/SSH) | [GitLab app scaling configuration](../high_availability/gitlab.md) | Yes |
|
||||
| [Prometheus](../../development/architecture.md#prometheus) and [Grafana](../../development/architecture.md#grafana) | GitLab environment monitoring | [Monitoring node for scaling](../high_availability/monitoring_node.md) | Yes |
|
||||
|
@ -217,9 +217,6 @@ column.
|
|||
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
|
||||
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).
|
||||
|
||||
1. AWS-equivalent and Azure-equivalent configurations are rough suggestions
|
||||
and may change in the future. They have not yet been tested and validated.
|
||||
|
||||
1. From GitLab 13.0, using NFS for Git repositories is deprecated. In GitLab
|
||||
14.0, support for NFS for Git repositories is scheduled to be removed.
|
||||
Upgrade to [Gitaly Cluster](../gitaly/praefect.md) as soon as possible.
|
||||
|
|
|
@ -121,13 +121,15 @@ Example response:
|
|||
"id":2,
|
||||
"name":"awesome-v0.2.msi",
|
||||
"url":"http://192.168.10.15:3000/msi",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
},
|
||||
{
|
||||
"id":1,
|
||||
"name":"awesome-v0.2.dmg",
|
||||
"url":"http://192.168.10.15:3000",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
}
|
||||
],
|
||||
"evidence_file_path":"https://gitlab.example.com/root/awesome-app/-/releases/v0.2/evidence.json"
|
||||
|
@ -323,7 +325,8 @@ Example response:
|
|||
"id":3,
|
||||
"name":"hoge",
|
||||
"url":"https://gitlab.example.com/root/awesome-app/-/tags/v0.11.1/binaries/linux-amd64",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -357,13 +360,14 @@ POST /projects/:id/releases
|
|||
| `assets:links:name`| string | required by: `assets:links` | The name of the link. |
|
||||
| `assets:links:url` | string | required by: `assets:links` | The URL of the link. |
|
||||
| `assets:links:filepath` | string | no | Optional path for a [Direct Asset link](../../user/project/releases.md).
|
||||
| `assets:links:link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`.
|
||||
| `released_at` | datetime | no | The date when the release will be/was ready. Defaults to the current time. Expected in ISO 8601 format (`2019-03-15T08:00:00Z`). |
|
||||
|
||||
Example request:
|
||||
|
||||
```shell
|
||||
curl --header 'Content-Type: application/json' --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \
|
||||
--data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "milestones": ["v1.0", "v1.0-rc"], "assets": { "links": [{ "name": "hoge", "url": "https://google.com", "filepath": "/binaries/linux-amd64" }] } }' \
|
||||
--data '{ "name": "New release", "tag_name": "v0.3", "description": "Super nice release", "milestones": ["v1.0", "v1.0-rc"], "assets": { "links": [{ "name": "hoge", "url": "https://google.com", "filepath": "/binaries/linux-amd64", "link_type":"other" }] } }' \
|
||||
--request POST https://gitlab.example.com/api/v4/projects/24/releases
|
||||
```
|
||||
|
||||
|
@ -465,7 +469,8 @@ Example response:
|
|||
"id":3,
|
||||
"name":"hoge",
|
||||
"url":"https://gitlab.example.com/root/awesome-app/-/tags/v0.11.1/binaries/linux-amd64",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
}
|
||||
],
|
||||
"evidence_file_path":"https://gitlab.example.com/root/awesome-app/-/releases/v0.3/evidence.json"
|
||||
|
|
|
@ -32,13 +32,15 @@ Example response:
|
|||
"id":2,
|
||||
"name":"awesome-v0.2.msi",
|
||||
"url":"http://192.168.10.15:3000/msi",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
},
|
||||
{
|
||||
"id":1,
|
||||
"name":"awesome-v0.2.dmg",
|
||||
"url":"http://192.168.10.15:3000",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
@ -70,7 +72,8 @@ Example response:
|
|||
"id":1,
|
||||
"name":"awesome-v0.2.dmg",
|
||||
"url":"http://192.168.10.15:3000",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -87,7 +90,8 @@ POST /projects/:id/releases/:tag_name/assets/links
|
|||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
|
||||
| `tag_name` | string | yes | The tag associated with the Release. |
|
||||
| `name` | string | yes | The name of the link. |
|
||||
| `url` | string | yes | The URL of the link. |
|
||||
| `url` | string | yes | The URL of the link. |
|
||||
| `link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
|
||||
|
||||
Example request:
|
||||
|
||||
|
@ -106,7 +110,8 @@ Example response:
|
|||
"id":1,
|
||||
"name":"awesome-v0.2.dmg",
|
||||
"url":"http://192.168.10.15:3000",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -122,9 +127,10 @@ PUT /projects/:id/releases/:tag_name/assets/links/:link_id
|
|||
| ------------- | -------------- | -------- | --------------------------------------- |
|
||||
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
|
||||
| `tag_name` | string | yes | The tag associated with the Release. |
|
||||
| `link_id` | integer | yes | The ID of the link. |
|
||||
| `link_id` | integer | yes | The ID of the link. |
|
||||
| `name` | string | no | The name of the link. |
|
||||
| `url` | string | no | The URL of the link. |
|
||||
| `url` | string | no | The URL of the link. |
|
||||
| `link_type` | string | no | The type of the link: `other`, `runbook`, `image`, `package`. Defaults to `other`. |
|
||||
|
||||
NOTE: **NOTE**
|
||||
You have to specify at least one of `name` or `url`
|
||||
|
@ -132,7 +138,7 @@ You have to specify at least one of `name` or `url`
|
|||
Example request:
|
||||
|
||||
```shell
|
||||
curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: n671WNGecHugsdEDPsyo" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
|
||||
curl --request PUT --data name="new name" --data link_type="runbook" --header "PRIVATE-TOKEN: n671WNGecHugsdEDPsyo" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
|
||||
```
|
||||
|
||||
Example response:
|
||||
|
@ -142,7 +148,8 @@ Example response:
|
|||
"id":1,
|
||||
"name":"new name",
|
||||
"url":"http://192.168.10.15:3000",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"runbook"
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -173,6 +180,7 @@ Example response:
|
|||
"id":1,
|
||||
"name":"new name",
|
||||
"url":"http://192.168.10.15:3000",
|
||||
"external":true
|
||||
"external":true,
|
||||
"link_type":"other"
|
||||
}
|
||||
```
|
||||
|
|
|
@ -63,7 +63,7 @@ Complementary reads:
|
|||
- [Working with Gitaly](gitaly.md)
|
||||
- [Manage feature flags](feature_flags/index.md)
|
||||
- [Licensed feature availability](licensed_feature_availability.md)
|
||||
- [View sent emails or preview mailers](emails.md)
|
||||
- [Dealing with email/mailers](emails.md)
|
||||
- [Shell commands](shell_commands.md) in the GitLab codebase
|
||||
- [`Gemfile` guidelines](gemfile.md)
|
||||
- [Pry debugging](pry_debugging.md)
|
||||
|
|
|
@ -1368,7 +1368,35 @@ versions back, you can consider removing the text if it's irrelevant or confusin
|
|||
For example, if the current major version is 12.x, version text referencing versions of GitLab 8.x
|
||||
and older are candidates for removal if necessary for clearer or cleaner docs.
|
||||
|
||||
## Product badges
|
||||
## Products and features
|
||||
|
||||
Refer to the information in this section when describing products and features
|
||||
within the GitLab product documentation.
|
||||
|
||||
### Avoid line breaks in names
|
||||
|
||||
When entering a product or feature name that includes a space (such as
|
||||
GitLab Community Edition) or even other companies' products (such as
|
||||
Amazon Web Services), be sure to not split the product or feature name across
|
||||
lines with an inserted line break. Splitting product or feature names across
|
||||
lines makes searching for these items more difficult, and can cause problems if
|
||||
names change.
|
||||
|
||||
For example, the followng Markdown content is *not* formatted correctly:
|
||||
|
||||
```markdown
|
||||
When entering a product or feature name that includes a space (such as GitLab
|
||||
Community Edition), don't split the product or feature name across lines.
|
||||
```
|
||||
|
||||
Instead, it should appear similar to the following:
|
||||
|
||||
```markdown
|
||||
When entering a product or feature name that includes a space (such as
|
||||
GitLab Community Edition), don't split the product or feature name across lines.
|
||||
```
|
||||
|
||||
### Product badges
|
||||
|
||||
When a feature is available in EE-only tiers, add the corresponding tier according to the
|
||||
feature availability:
|
||||
|
@ -1408,7 +1436,7 @@ For example:
|
|||
The absence of tiers' mentions mean that the feature is available in GitLab Core,
|
||||
GitLab.com Free, and all higher tiers.
|
||||
|
||||
### How it works
|
||||
#### How it works
|
||||
|
||||
Introduced by [!244](https://gitlab.com/gitlab-org/gitlab-docs/-/merge_requests/244),
|
||||
the special markup `**(STARTER)**` will generate a `span` element to trigger the
|
||||
|
|
|
@ -1,5 +1,20 @@
|
|||
# Dealing with email in development
|
||||
|
||||
## Ensuring compatibility with mailer Sidekiq jobs
|
||||
|
||||
A Sidekiq job is enqueued whenever `deliver_later` is called on an `ActionMailer`.
|
||||
If a mailer argument needs to be added or removed, it is important to ensure
|
||||
both backward and forward compatibility. Adhere to the Sidekiq Style Guide steps for
|
||||
[changing the arguments for a worker](sidekiq_style_guide.md#changing-the-arguments-for-a-worker).
|
||||
|
||||
In the following example from [`NotificationService`](https://gitlab.com/gitlab-org/gitlab/-/blob/33ccb22e4fc271dbaac94b003a7a1a2915a13441/app/services/notification_service.rb#L74)
|
||||
adding or removing an argument in this mailer's definition may cause problems
|
||||
during deployment before all Rails and Sidekiq nodes have the updated code.
|
||||
|
||||
```ruby
|
||||
mailer.unknown_sign_in_email(user, ip, time).deliver_later
|
||||
```
|
||||
|
||||
## Sent emails
|
||||
|
||||
To view rendered emails "sent" in your development instance, visit
|
||||
|
|
|
@ -537,18 +537,74 @@ possible situations:
|
|||
|
||||
### Changing the arguments for a worker
|
||||
|
||||
Jobs need to be backwards- and forwards-compatible between consecutive versions
|
||||
of the application.
|
||||
Jobs need to be backward and forward compatible between consecutive versions
|
||||
of the application. Adding or removing an argument may cause problems
|
||||
during deployment before all Rails and Sidekiq nodes have the updated code.
|
||||
|
||||
This can be done by following this process:
|
||||
#### Remove an argument
|
||||
|
||||
1. **Do not remove arguments from the `perform` function.**. Instead, use the
|
||||
following approach
|
||||
1. Provide a default value (usually `nil`) and use a comment to mark the
|
||||
argument as deprecated
|
||||
1. Stop using the argument in `perform_async`.
|
||||
1. Ignore the value in the worker class, but do not remove it until the next
|
||||
major release.
|
||||
**Do not remove arguments from the `perform` function.**. Instead, use the
|
||||
following approach:
|
||||
|
||||
1. Provide a default value (usually `nil`) and use a comment to mark the
|
||||
argument as deprecated
|
||||
1. Stop using the argument in `perform_async`.
|
||||
1. Ignore the value in the worker class, but do not remove it until the next
|
||||
major release.
|
||||
|
||||
In the following example, if you want to remove `arg2`, first set a `nil` default value,
|
||||
and then update locations where `ExampleWorker.perform_async` is called.
|
||||
|
||||
```ruby
|
||||
class ExampleWorker
|
||||
def perform(object_id, arg1, arg2 = nil)
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
#### Add an argument
|
||||
|
||||
There are two options for safely adding new arguments to Sidekiq workers:
|
||||
|
||||
1. Set up a [multi-step deployment](#multi-step-deployment) in which the new argument is first added to the worker
|
||||
1. Use a [parameter hash](#parameter-hash) for additional arguments. This is perhaps the most flexible option.
|
||||
1. Use a parameter hash for additional arguments. This is perhaps the most flexible option.
|
||||
|
||||
##### Multi-step deployment
|
||||
|
||||
This approach requires multiple merge requests and for the first merge request
|
||||
to be merged and deployed before additional changes are merged.
|
||||
|
||||
1. In an initial merge request, add the argument to the worker with a default
|
||||
value:
|
||||
|
||||
```ruby
|
||||
class ExampleWorker
|
||||
def perform(object_id, new_arg = nil)
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
1. Merge and deploy the worker with the new argument.
|
||||
1. In a further merge request, update `ExampleWorker.perform_async` calls to
|
||||
use the new argument.
|
||||
|
||||
##### Parameter hash
|
||||
|
||||
This approach will not require multiple deployments if an existing worker already
|
||||
utilizes a parameter hash.
|
||||
|
||||
1. Use a parameter hash in the worker to allow for future flexibility:
|
||||
|
||||
```ruby
|
||||
class ExampleWorker
|
||||
def perform(object_id, params = {})
|
||||
# ...
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
### Removing workers
|
||||
|
||||
|
|
|
@ -7,47 +7,49 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
# Incident Management
|
||||
|
||||
GitLab offers solutions for handling incidents in your applications and services,
|
||||
from setting up an alert with Prometheus, to receiving a notification through a
|
||||
monitoring tool like Slack, and [setting up Zoom calls](#zoom-integration-in-issues) with your
|
||||
support team. Incidents can display [metrics](#embed-metrics-in-incidents-and-issues)
|
||||
and [logs](#view-logs-from-metrics-panel).
|
||||
such as setting up Prometheus alerts, displaying metrics, and sending notifications.
|
||||
|
||||
## Configure incidents **(ULTIMATE)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/4925) in GitLab Ultimate 11.11.
|
||||
|
||||
You can enable or disable Incident Management features in your project's
|
||||
**{settings}** **Settings > Operations > Incidents**. Issues can be created for
|
||||
each alert triggered, and separate email notifications can be sent to users with
|
||||
[Developer permissions](../permissions.md). Appropriately configured alerts include an
|
||||
You can enable or disable Incident Management features in the GitLab user interface
|
||||
to create issues when alerts are triggered:
|
||||
|
||||
1. Navigate to **{settings}** **Settings > Operations > Incidents** and expand
|
||||
**Incidents**:
|
||||
|
||||
![Incident Management Settings](img/incident_management_settings.png)
|
||||
|
||||
1. For GitLab versions 11.11 and greater, you can select the **Create an issue**
|
||||
checkbox to create an issue based on your own
|
||||
[issue templates](../project/description_templates.md#creating-issue-templates).
|
||||
For more information, see
|
||||
[Taking Action on Incidents](../project/integrations/prometheus.md#taking-action-on-incidents-ultimate) **(ULTIMATE)**.
|
||||
1. To create issues from alerts, select the template in the **Issue Template**
|
||||
select box.
|
||||
1. To send [separate email notifications](#notify-developers-of-alerts) to users
|
||||
with [Developer permissions](../permissions.md), select
|
||||
**Send a separate email notification to Developers**.
|
||||
1. Click **Save changes**.
|
||||
|
||||
Appropriately configured alerts include an
|
||||
[embedded chart](../project/integrations/prometheus.md#embedding-metrics-based-on-alerts-in-incident-issues)
|
||||
for the query corresponding to the alert. You can also configure GitLab to
|
||||
[close issues](../project/integrations/prometheus.md#taking-action-on-incidents-ultimate)
|
||||
when you receive notification that the alert is resolved.
|
||||
|
||||
![Incident Management Settings](img/incident_management_settings.png)
|
||||
|
||||
### Create issues from alerts
|
||||
|
||||
You can create GitLab issues from an alert notification. These issues contain
|
||||
information about the alerts to help you diagnose the source of the alerts.
|
||||
|
||||
1. Visit your project's **{settings}** **Settings > Operations > Incidents**.
|
||||
1. Select the **Create an issue** checkbox for GitLab to create an issue from
|
||||
the incident.
|
||||
1. Select the template from the **Issue Template** dropdown.
|
||||
You can create your own [issue templates](../project/description_templates.md#creating-issue-templates)
|
||||
to [use within Incident Management](../project/integrations/prometheus.md#taking-action-on-incidents-ultimate).
|
||||
1. Click **Save changes**.
|
||||
|
||||
## Notify developers of alerts
|
||||
### Notify developers of alerts
|
||||
|
||||
GitLab can react to the alerts triggered from your applications and services
|
||||
by creating issues and alerting developers through email. GitLab sends these emails
|
||||
to [owners and maintainers](../permissions.md) of the project. They contain details
|
||||
of the alert, and a link for more information.
|
||||
by creating issues and alerting developers through email. By default, GitLab
|
||||
sends these emails to [owners and maintainers](../permissions.md) of the project.
|
||||
These emails contain details of the alert, and a link for more information.
|
||||
|
||||
### Configure Prometheus alerts
|
||||
To send separate email notifications to users with
|
||||
[Developer permissions](../permissions.md), see [Configure incidents](#configure-incidents-ultimate).
|
||||
|
||||
## Configure Prometheus alerts
|
||||
|
||||
You can set up Prometheus alerts in:
|
||||
|
||||
|
@ -57,7 +59,7 @@ You can set up Prometheus alerts in:
|
|||
Prometheus alerts are created by the special Alert Bot user. You can't remove this
|
||||
user, but it does not count toward your license limit.
|
||||
|
||||
### Configure external generic alerts
|
||||
## Configure external generic alerts
|
||||
|
||||
GitLab can accept alerts from any source through a generic webhook receiver. When
|
||||
[configuring the generic alerts integration](../project/integrations/generic_alerts.md),
|
||||
|
@ -65,7 +67,7 @@ GitLab creates a unique endpoint which receives a JSON-formatted, customizable p
|
|||
|
||||
## Embed metrics in incidents and issues
|
||||
|
||||
You can embed metrics anywhere GitLab Markdown is used, such as descriptions,
|
||||
You can embed metrics anywhere [GitLab Markdown](../markdown.md) is used, such as descriptions,
|
||||
comments on issues, and merge requests. Embedding metrics helps you share them
|
||||
when discussing incidents or performance issues. You can output the dashboard directly
|
||||
into any issue, merge request, epic, or any other Markdown text field in GitLab
|
||||
|
@ -78,10 +80,9 @@ in incidents and issue templates.
|
|||
|
||||
### Context menu
|
||||
|
||||
From each of the embedded metrics panels, you can access more details
|
||||
about the data you're viewing from a context menu. You can access the context menu
|
||||
by clicking the **{ellipsis_v}** **More actions** dropdown box above the
|
||||
upper right corner of the panel. The options are:
|
||||
You can view more details about an embedded metrics panel from the context menu.
|
||||
To access the context menu, click the **{ellipsis_v}** **More actions** dropdown box
|
||||
above the upper right corner of the panel. The options are:
|
||||
|
||||
- [View logs](#view-logs-from-metrics-panel).
|
||||
- **Download CSV** - Data from embedded charts can be
|
||||
|
@ -97,16 +98,16 @@ incident and need to [explore logs](../project/integrations/prometheus.md#view-l
|
|||
from across your application. These logs help you understand what is affecting
|
||||
your application's performance and resolve any problems.
|
||||
|
||||
## Slack integration
|
||||
## Integrate incidents with Slack
|
||||
|
||||
Slack slash commands allow you to control GitLab and view GitLab content without leaving Slack.
|
||||
|
||||
Learn how to [set up Slack slash commands](../project/integrations/slack_slash_commands.md)
|
||||
and how to [use the available slash commands](../../integration/slash_commands.md).
|
||||
|
||||
## Zoom integration in issues
|
||||
## Integrate issues with Zoom
|
||||
|
||||
GitLab enables you to [associate a Zoom meeting with an issue](../project/issues/associate_zoom_meeting.md)
|
||||
for synchronous communication during incident management. After starting a Zoom
|
||||
call for an incident, you can associate the conference call with an issue, so your
|
||||
team members can join without requesting a link.
|
||||
call for an incident, you can associate the conference call with an issue. Your
|
||||
team members can join the Zoom call without requesting a link.
|
||||
|
|
|
@ -177,6 +177,22 @@ Different discussions have different pin numbers:
|
|||
From GitLab 12.5 on, new discussions will be outputted to the issue activity,
|
||||
so that everyone involved can participate in the discussion.
|
||||
|
||||
## Resolve Design threads
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13049) in GitLab 13.1.
|
||||
|
||||
Discussion threads can be resolved on Designs. You can mark a thread as resolved
|
||||
or unresolved by clicking the **Resolve thread** icon at the first comment of the
|
||||
discussion.
|
||||
|
||||
![Resolve thread icon](img/resolve_design-discussion_icon_v13_1.png)
|
||||
|
||||
Pinned comments can also be resolved or unresolved in their threads.
|
||||
When replying to a comment, you will see a checkbox that you can click in order to resolve or unresolve
|
||||
the thread once published.
|
||||
|
||||
![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
|
||||
|
||||
## Referring to designs in Markdown
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217160) in **GitLab 13.1**.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
Binary file not shown.
After Width: | Height: | Size: 36 KiB |
|
@ -68,7 +68,8 @@ If you have the correct access and a Premium license, you have the option to set
|
|||
Follow these steps to do so:
|
||||
|
||||
1. [Set up incoming email](../../administration/incoming_email.md#set-it-up) for the GitLab instance.
|
||||
This must support [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing).
|
||||
- We recommend using [email sub-addressing](../../administration/incoming_email.md#email-sub-addressing),
|
||||
but in GitLab 11.7 and later you can also use [catch-all mailboxes](../../administration/incoming_email.md#catch-all-mailbox).
|
||||
1. Navigate to your project's **Settings > General** and locate the **Service Desk** section.
|
||||
1. Enable the **Activate Service Desk** toggle. This reveals a unique email address to email issues
|
||||
to the project. These issues will be [confidential](issues/confidential_issues.md), so they will
|
||||
|
|
|
@ -66,7 +66,7 @@ module Banzai
|
|||
# links have `gfm` and `gfm-issue` class names attached for styling.
|
||||
def issue_link_filter(text, link_content: nil)
|
||||
self.class.references_in(text, issue_reference_pattern) do |match, id|
|
||||
url = url_for_issue(id, project, only_path: context[:only_path])
|
||||
url = url_for_issue(id)
|
||||
klass = reference_class(:issue)
|
||||
data = data_attribute(project: project.id, external_issue: id)
|
||||
content = link_content || match
|
||||
|
@ -77,8 +77,19 @@ module Banzai
|
|||
end
|
||||
end
|
||||
|
||||
def url_for_issue(*args)
|
||||
IssuesHelper.url_for_issue(*args)
|
||||
def url_for_issue(issue_id)
|
||||
return '' if project.nil?
|
||||
|
||||
url = if only_path?
|
||||
project.external_issue_tracker.issue_path(issue_id)
|
||||
else
|
||||
project.external_issue_tracker.issue_url(issue_id)
|
||||
end
|
||||
|
||||
# Ensure we return a valid URL to prevent possible XSS.
|
||||
URI.parse(url).to_s
|
||||
rescue URI::InvalidURIError
|
||||
''
|
||||
end
|
||||
|
||||
def default_issues_tracker?
|
||||
|
|
|
@ -18,7 +18,9 @@ module Banzai
|
|||
end
|
||||
|
||||
def url_for_object(issue, project)
|
||||
IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path], internal: true)
|
||||
return issue_path(issue, project) if only_path?
|
||||
|
||||
issue_url(issue, project)
|
||||
end
|
||||
|
||||
def projects_relation_for_paths(paths)
|
||||
|
@ -35,6 +37,14 @@ module Banzai
|
|||
|
||||
private
|
||||
|
||||
def issue_path(issue, project)
|
||||
Gitlab::Routing.url_helpers.namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: issue.iid)
|
||||
end
|
||||
|
||||
def issue_url(issue, project)
|
||||
Gitlab::Routing.url_helpers.namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: issue.iid)
|
||||
end
|
||||
|
||||
def design_link_extras(issue, path)
|
||||
if path == '/designs' && read_designs?(issue)
|
||||
['designs']
|
||||
|
|
|
@ -142,6 +142,12 @@ module Banzai
|
|||
def element_node?(node)
|
||||
node.is_a?(Nokogiri::XML::Element)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def only_path?
|
||||
context[:only_path]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4451,9 +4451,6 @@ msgstr ""
|
|||
msgid "Click the button below to begin the install process by navigating to the Kubernetes page"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click the image where you'd like to start a new discussion"
|
||||
msgstr ""
|
||||
|
||||
msgid "Click to expand it."
|
||||
msgstr ""
|
||||
|
||||
|
@ -5528,6 +5525,9 @@ msgstr ""
|
|||
msgid "Collapse child epics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Collapse replies"
|
||||
msgstr ""
|
||||
|
||||
msgid "Collapse sidebar"
|
||||
msgstr ""
|
||||
|
||||
|
@ -7519,9 +7519,15 @@ msgstr ""
|
|||
msgid "DesignManagement|Cancel comment update confirmation"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Click the image where you'd like to start a new discussion"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Comments you resolve can be viewed and unresolved by going to the \"Resolved Comments\" section below"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Could not add a new comment. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -7567,9 +7573,18 @@ msgstr ""
|
|||
msgid "DesignManagement|Keep comment"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Learn more about resolving comments"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Requested design version does not exist. Showing latest version instead"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Resolve thread"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Resolved Comments"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Save comment"
|
||||
msgstr ""
|
||||
|
||||
|
@ -7582,6 +7597,9 @@ msgstr ""
|
|||
msgid "DesignManagement|To enable design management, you'll need to %{requirements_link_start}meet the requirements%{requirements_link_end}. If you need help, reach out to our %{support_link_start}support team%{support_link_end} for assistance."
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Unresolve thread"
|
||||
msgstr ""
|
||||
|
||||
msgid "DesignManagement|Upload designs"
|
||||
msgstr ""
|
||||
|
||||
|
@ -18657,6 +18675,9 @@ msgstr ""
|
|||
msgid "Resolved all discussions."
|
||||
msgstr ""
|
||||
|
||||
msgid "Resolved by"
|
||||
msgstr ""
|
||||
|
||||
msgid "Resolved by %{name}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -19423,6 +19444,9 @@ msgstr ""
|
|||
msgid "SecurityReports|Status"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|The rating \"unknown\" indicates that the underlying scanner doesn’t contain or provide a severity rating."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityReports|The security dashboard displays the latest security findings for projects you wish to monitor. Select \"Edit dashboard\" to add and remove projects."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
module QA
|
||||
module Resource
|
||||
class ProjectMilestone < Base
|
||||
attr_writer :start_date, :due_date
|
||||
|
||||
attribute :id
|
||||
attribute :title
|
||||
|
||||
|
@ -27,7 +29,10 @@ module QA
|
|||
def api_post_body
|
||||
{
|
||||
title: title
|
||||
}
|
||||
}.tap do |hash|
|
||||
hash[:start_date] = @start_date if @start_date
|
||||
hash[:due_date] = @due_date if @due_date
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe InstanceStatistics::DevOpsScoreController do
|
||||
RSpec.describe InstanceStatistics::DevOpsScoreController do
|
||||
it_behaves_like 'instance statistics availability'
|
||||
end
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe InvitesController do
|
||||
RSpec.describe InvitesController do
|
||||
let(:token) { '123456' }
|
||||
let(:user) { create(:user) }
|
||||
let(:member) { create(:project_member, invite_token: token, invite_email: 'test@abc.com', user: user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Ldap::OmniauthCallbacksController do
|
||||
RSpec.describe Ldap::OmniauthCallbacksController do
|
||||
include_context 'Ldap::OmniauthCallbacksController'
|
||||
|
||||
it 'allows sign in' do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe MetricsController, :request_store do
|
||||
RSpec.describe MetricsController, :request_store do
|
||||
include StubENV
|
||||
|
||||
let(:metrics_multiproc_dir) { @metrics_multiproc_dir }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe NotificationSettingsController do
|
||||
RSpec.describe NotificationSettingsController do
|
||||
let(:project) { create(:project) }
|
||||
let(:group) { create(:group, :internal) }
|
||||
let(:user) { create(:user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Oauth::ApplicationsController do
|
||||
RSpec.describe Oauth::ApplicationsController do
|
||||
let(:user) { create(:user) }
|
||||
let(:application) { create(:oauth_application, owner: user) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Oauth::AuthorizationsController do
|
||||
RSpec.describe Oauth::AuthorizationsController do
|
||||
let!(:application) { create(:oauth_application, scopes: 'api read_user', redirect_uri: 'http://example.com') }
|
||||
let(:params) do
|
||||
{
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Oauth::AuthorizedApplicationsController do
|
||||
RSpec.describe Oauth::AuthorizedApplicationsController do
|
||||
let(:user) { create(:user) }
|
||||
let(:guest) { create(:user) }
|
||||
let(:application) { create(:oauth_application, owner: guest) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe OmniauthCallbacksController, type: :controller do
|
||||
RSpec.describe OmniauthCallbacksController, type: :controller do
|
||||
include LoginHelpers
|
||||
|
||||
describe 'omniauth' do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe PasswordsController do
|
||||
RSpec.describe PasswordsController do
|
||||
describe '#check_password_authentication_available' do
|
||||
before do
|
||||
@request.env["devise.mapping"] = Devise.mappings[:user]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::AccountsController do
|
||||
RSpec.describe Profiles::AccountsController do
|
||||
describe 'DELETE unlink' do
|
||||
let(:user) { create(:omniauth_user) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::AvatarsController do
|
||||
RSpec.describe Profiles::AvatarsController do
|
||||
let(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png")) }
|
||||
|
||||
before do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::EmailsController do
|
||||
RSpec.describe Profiles::EmailsController do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::KeysController do
|
||||
RSpec.describe Profiles::KeysController do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe 'POST #create' do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::NotificationsController do
|
||||
RSpec.describe Profiles::NotificationsController do
|
||||
let(:user) do
|
||||
create(:user) do |user|
|
||||
user.emails.create(email: 'original@example.com', confirmed_at: Time.current)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::PersonalAccessTokensController do
|
||||
RSpec.describe Profiles::PersonalAccessTokensController do
|
||||
let(:user) { create(:user) }
|
||||
let(:token_attributes) { attributes_for(:personal_access_token) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::PreferencesController do
|
||||
RSpec.describe Profiles::PreferencesController do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Profiles::TwoFactorAuthsController do
|
||||
RSpec.describe Profiles::TwoFactorAuthsController do
|
||||
before do
|
||||
# `user` should be defined within the action-specific describe blocks
|
||||
sign_in(user)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require('spec_helper')
|
||||
|
||||
describe ProfilesController, :request_store do
|
||||
RSpec.describe ProfilesController, :request_store do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
describe 'POST update' do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::AlertManagementController do
|
||||
RSpec.describe Projects::AlertManagementController do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:role) { :developer }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::Alerting::NotificationsController do
|
||||
RSpec.describe Projects::Alerting::NotificationsController do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:environment) { create(:environment, project: project) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::ArtifactsController do
|
||||
RSpec.describe Projects::ArtifactsController do
|
||||
include RepoHelpers
|
||||
|
||||
let(:user) { project.owner }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::AutocompleteSourcesController do
|
||||
RSpec.describe Projects::AutocompleteSourcesController do
|
||||
let_it_be(:group) { create(:group) }
|
||||
let_it_be(:project) { create(:project, namespace: group) }
|
||||
let_it_be(:issue) { create(:issue, project: project) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::AvatarsController do
|
||||
RSpec.describe Projects::AvatarsController do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
|
||||
before do
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::BadgesController do
|
||||
RSpec.describe Projects::BadgesController do
|
||||
let(:project) { pipeline.project }
|
||||
let!(:pipeline) { create(:ci_empty_pipeline) }
|
||||
let(:user) { create(:user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::BlameController do
|
||||
RSpec.describe Projects::BlameController do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::BlobController do
|
||||
RSpec.describe Projects::BlobController do
|
||||
include ProjectForksHelper
|
||||
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::BoardsController do
|
||||
RSpec.describe Projects::BoardsController do
|
||||
let(:project) { create(:project) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::BranchesController do
|
||||
RSpec.describe Projects::BranchesController do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
let(:developer) { create(:user) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::Ci::DailyBuildGroupReportResultsController do
|
||||
RSpec.describe Projects::Ci::DailyBuildGroupReportResultsController do
|
||||
describe 'GET index' do
|
||||
let(:project) { create(:project, :public, :repository) }
|
||||
let(:ref_path) { 'refs/heads/master' }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::Ci::LintsController do
|
||||
RSpec.describe Projects::Ci::LintsController do
|
||||
include StubRequests
|
||||
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::Clusters::ApplicationsController do
|
||||
RSpec.describe Projects::Clusters::ApplicationsController do
|
||||
include AccessMatchersForController
|
||||
|
||||
def current_application
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::ClustersController do
|
||||
RSpec.describe Projects::ClustersController do
|
||||
include AccessMatchersForController
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
include KubernetesHelpers
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::CommitController do
|
||||
RSpec.describe Projects::CommitController do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Projects::CommitsController do
|
||||
RSpec.describe Projects::CommitsController do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue