Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
6e320396b2
commit
a1908447b7
|
|
@ -1 +1 @@
|
|||
2c750a230dae024d4f59a85c7dba66bac5546fe6
|
||||
6689311d652362fc41e5b5cb53aeffede352c8b7
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ export default {
|
|||
'board-type-assignee': list.type === 'assignee',
|
||||
}"
|
||||
:data-id="list.id"
|
||||
class="board gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal"
|
||||
class="board gl-display-inline-block gl-h-full gl-px-3 gl-vertical-align-top gl-white-space-normal"
|
||||
data-qa-selector="board_list"
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default {
|
|||
|
||||
<template>
|
||||
<div class="append-bottom-20">
|
||||
<label class="form-section-title label-bold" for="board-new-name">
|
||||
<label class="label-bold gl-font-lg" for="board-new-name">
|
||||
{{ __('List options') }}
|
||||
</label>
|
||||
<p class="text-secondary gl-mb-3">
|
||||
|
|
|
|||
|
|
@ -196,9 +196,7 @@ export default {
|
|||
<p v-if="isDeleteForm">{{ __('Are you sure you want to delete this board?') }}</p>
|
||||
<form v-else class="js-board-config-modal" @submit.prevent>
|
||||
<div v-if="!readonly" class="append-bottom-20">
|
||||
<label class="form-section-title label-bold" for="board-new-name">{{
|
||||
__('Title')
|
||||
}}</label>
|
||||
<label class="label-bold gl-font-lg" for="board-new-name">{{ __('Title') }}</label>
|
||||
<input
|
||||
id="board-new-name"
|
||||
ref="name"
|
||||
|
|
|
|||
|
|
@ -188,8 +188,9 @@ export default {
|
|||
'gl-py-3 gl-h-full': !list.isExpanded && !isSwimlanesHeader,
|
||||
'gl-border-b-0': !list.isExpanded || isSwimlanesHeader,
|
||||
'gl-py-2': !list.isExpanded && isSwimlanesHeader,
|
||||
'gl-flex-direction-column': !list.isExpanded,
|
||||
}"
|
||||
class="board-title gl-m-0 gl-display-flex js-board-handle"
|
||||
class="board-title gl-m-0 gl-display-flex gl-align-items-center gl-font-base gl-px-3 js-board-handle"
|
||||
>
|
||||
<gl-button
|
||||
v-if="list.isExpandable"
|
||||
|
|
@ -202,7 +203,15 @@ export default {
|
|||
@click="toggleExpanded"
|
||||
/>
|
||||
<!-- The following is only true in EE and if it is a milestone -->
|
||||
<span v-if="showMilestoneListDetails" aria-hidden="true" class="gl-mr-2 milestone-icon">
|
||||
<span
|
||||
v-if="showMilestoneListDetails"
|
||||
aria-hidden="true"
|
||||
class="milestone-icon"
|
||||
:class="{
|
||||
'gl-mt-3 gl-rotate-90': !list.isExpanded,
|
||||
'gl-mr-2': list.isExpanded,
|
||||
}"
|
||||
>
|
||||
<gl-icon name="timer" />
|
||||
</span>
|
||||
|
||||
|
|
@ -210,6 +219,9 @@ export default {
|
|||
v-if="showAssigneeListDetails"
|
||||
:href="list.assignee.path"
|
||||
class="user-avatar-link js-no-trigger"
|
||||
:class="{
|
||||
'gl-mt-3 gl-rotate-90': !list.isExpanded,
|
||||
}"
|
||||
>
|
||||
<img
|
||||
v-gl-tooltip.hover.bottom
|
||||
|
|
@ -223,20 +235,28 @@ export default {
|
|||
</a>
|
||||
<div
|
||||
class="board-title-text"
|
||||
:class="{ 'gl-display-none': !list.isExpanded && isSwimlanesHeader }"
|
||||
:class="{
|
||||
'gl-display-none': !list.isExpanded && isSwimlanesHeader,
|
||||
'gl-flex-grow-0 gl-my-3 gl-mx-0': !list.isExpanded,
|
||||
'gl-flex-grow-1': list.isExpanded,
|
||||
}"
|
||||
>
|
||||
<span
|
||||
v-if="list.type !== 'label'"
|
||||
v-gl-tooltip.hover
|
||||
:class="{
|
||||
'gl-display-inline-block': list.type === 'milestone',
|
||||
'gl-display-block': !list.isExpanded || list.type === 'milestone',
|
||||
}"
|
||||
:title="listTitle"
|
||||
class="board-title-main-text block-truncated"
|
||||
class="board-title-main-text gl-text-truncate"
|
||||
>
|
||||
{{ list.title }}
|
||||
</span>
|
||||
<span v-if="list.type === 'assignee'" class="board-title-sub-text gl-ml-2">
|
||||
<span
|
||||
v-if="list.type === 'assignee'"
|
||||
class="board-title-sub-text gl-ml-2 gl-font-weight-normal"
|
||||
:class="{ 'gl-display-none': !list.isExpanded }"
|
||||
>
|
||||
@{{ listAssignee }}
|
||||
</span>
|
||||
<gl-label
|
||||
|
|
@ -279,7 +299,10 @@ export default {
|
|||
<div
|
||||
v-if="showBoardListAndBoardInfo"
|
||||
class="issue-count-badge gl-display-inline-flex gl-pr-0 no-drag text-secondary"
|
||||
:class="{ 'gl-display-none!': !list.isExpanded && isSwimlanesHeader }"
|
||||
:class="{
|
||||
'gl-display-none!': !list.isExpanded && isSwimlanesHeader,
|
||||
'gl-p-0': !list.isExpanded,
|
||||
}"
|
||||
>
|
||||
<span class="gl-display-inline-flex">
|
||||
<gl-tooltip :target="() => $refs.issueCount" :title="issuesTooltipLabel" />
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ export default {
|
|||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex board-card-header" dir="auto">
|
||||
<div class="gl-display-flex" dir="auto">
|
||||
<h4 class="board-card-title gl-mb-0 gl-mt-0">
|
||||
<gl-icon
|
||||
v-if="issue.blocked"
|
||||
|
|
@ -156,7 +156,7 @@ export default {
|
|||
}}</a>
|
||||
</h4>
|
||||
</div>
|
||||
<div v-if="showLabelFooter" class="board-card-labels gl-mt-2 d-flex flex-wrap">
|
||||
<div v-if="showLabelFooter" class="board-card-labels gl-mt-2 gl-display-flex gl-flex-wrap">
|
||||
<template v-for="label in orderedLabels">
|
||||
<gl-label
|
||||
:key="label.id"
|
||||
|
|
@ -169,24 +169,26 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="board-card-footer d-flex justify-content-between align-items-end">
|
||||
<div
|
||||
class="board-card-footer gl-display-flex gl-justify-content-space-between gl-align-items-flex-end"
|
||||
>
|
||||
<div
|
||||
class="d-flex align-items-start flex-wrap-reverse board-card-number-container overflow-hidden js-board-card-number-container"
|
||||
class="gl-display-flex align-items-start flex-wrap-reverse board-card-number-container gl-overflow-hidden js-board-card-number-container"
|
||||
>
|
||||
<span
|
||||
v-if="issue.referencePath"
|
||||
class="board-card-number overflow-hidden d-flex gl-mr-3 gl-mt-3"
|
||||
class="board-card-number gl-overflow-hidden gl-display-flex gl-mr-3 gl-mt-3"
|
||||
>
|
||||
<tooltip-on-truncate
|
||||
v-if="issueReferencePath"
|
||||
:title="issueReferencePath"
|
||||
placement="bottom"
|
||||
class="board-issue-path block-truncated bold"
|
||||
class="board-issue-path gl-text-truncate gl-font-weight-bold"
|
||||
>{{ issueReferencePath }}</tooltip-on-truncate
|
||||
>
|
||||
#{{ issue.iid }}
|
||||
</span>
|
||||
<span class="board-info-items gl-mt-3 d-inline-block">
|
||||
<span class="board-info-items gl-mt-3 gl-display-inline-block">
|
||||
<issue-due-date v-if="issue.dueDate" :date="issue.dueDate" :closed="issue.closed" />
|
||||
<issue-time-estimate v-if="issue.timeEstimate" :estimate="issue.timeEstimate" />
|
||||
<issue-card-weight
|
||||
|
|
@ -196,7 +198,7 @@ export default {
|
|||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="board-card-assignee d-flex">
|
||||
<div class="board-card-assignee gl-display-flex">
|
||||
<user-avatar-link
|
||||
v-for="(assignee, index) in issue.assignees"
|
||||
v-if="shouldRenderAssignee(index)"
|
||||
|
|
@ -209,7 +211,7 @@ export default {
|
|||
tooltip-placement="bottom"
|
||||
>
|
||||
<span class="js-assignee-tooltip">
|
||||
<span class="bold d-block">{{ __('Assignee') }}</span>
|
||||
<span class="gl-font-weight-bold gl-display-block">{{ __('Assignee') }}</span>
|
||||
{{ assignee.name }}
|
||||
<span class="text-white-50">@{{ assignee.username }}</span>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ function getErrorMessage(res) {
|
|||
|
||||
export default function dropzoneInput(form, config = { parallelUploads: 2 }) {
|
||||
const divHover = '<div class="div-dropzone-hover"></div>';
|
||||
const iconPaperclip = spriteIcon('paperclip', 'div-dropzone-icon');
|
||||
const iconPaperclip = spriteIcon('paperclip', 'div-dropzone-icon s24');
|
||||
const $attachButton = form.find('.button-attach-file');
|
||||
const $attachingFileMessage = form.find('.attaching-file-message');
|
||||
const $cancelButton = form.find('.button-cancel-uploading-files');
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script>
|
||||
import GetBlobContent from 'shared_queries/snippet/snippet_blob_content.query.graphql';
|
||||
|
||||
import BlobHeader from '~/blob/components/blob_header.vue';
|
||||
import BlobContent from '~/blob/components/blob_content.vue';
|
||||
|
||||
import GetBlobContent from '../queries/snippet.blob.content.query.graphql';
|
||||
|
||||
import {
|
||||
SIMPLE_BLOB_VIEWER,
|
||||
RICH_BLOB_VIEWER,
|
||||
|
|
@ -21,7 +21,7 @@ export default {
|
|||
query: GetBlobContent,
|
||||
variables() {
|
||||
return {
|
||||
ids: this.snippet.id,
|
||||
ids: [this.snippet.id],
|
||||
rich: this.activeViewerType === RICH_BLOB_VIEWER,
|
||||
paths: [this.blob.path],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,12 +11,12 @@ import {
|
|||
GlButton,
|
||||
GlTooltipDirective,
|
||||
} from '@gitlab/ui';
|
||||
import CanCreatePersonalSnippet from 'shared_queries/snippet/user_permissions.query.graphql';
|
||||
import CanCreateProjectSnippet from 'shared_queries/snippet/project_permissions.query.graphql';
|
||||
import { __ } from '~/locale';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
import DeleteSnippetMutation from '../mutations/deleteSnippet.mutation.graphql';
|
||||
import CanCreatePersonalSnippet from '../queries/userPermissions.query.graphql';
|
||||
import CanCreateProjectSnippet from '../queries/projectPermissions.query.graphql';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { fetchPolicies } from '~/lib/graphql';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import GetSnippetQuery from '../queries/snippet.query.graphql';
|
||||
import GetSnippetQuery from 'shared_queries/snippet/snippet.query.graphql';
|
||||
|
||||
const blobsDefault = [];
|
||||
|
||||
|
|
@ -8,7 +8,7 @@ export const getSnippetMixin = {
|
|||
query: GetSnippetQuery,
|
||||
variables() {
|
||||
return {
|
||||
ids: this.snippetGid,
|
||||
ids: [this.snippetGid],
|
||||
};
|
||||
},
|
||||
update: data => {
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
#import '../fragments/snippetBase.fragment.graphql'
|
||||
#import '../fragments/project.fragment.graphql'
|
||||
#import "~/graphql_shared/fragments/author.fragment.graphql"
|
||||
|
||||
query GetSnippetQuery($ids: [ID!]) {
|
||||
snippets(ids: $ids) {
|
||||
nodes {
|
||||
...SnippetBase
|
||||
...SnippetProject
|
||||
author {
|
||||
...Author
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,17 +61,17 @@ export default {
|
|||
<span v-if="canAttachFile" class="uploading-container">
|
||||
<span class="uploading-progress-container hide">
|
||||
<template>
|
||||
<gl-icon name="media" :size="16" class="gl-vertical-align-text-bottom" />
|
||||
<gl-icon name="media" />
|
||||
</template>
|
||||
<span class="attaching-file-message"></span>
|
||||
<!-- eslint-disable-next-line @gitlab/vue-require-i18n-strings -->
|
||||
<span class="uploading-progress">0%</span>
|
||||
<gl-loading-icon inline class="align-text-bottom" />
|
||||
<gl-loading-icon inline />
|
||||
</span>
|
||||
<span class="uploading-error-container hide">
|
||||
<span class="uploading-error-icon">
|
||||
<template>
|
||||
<gl-icon name="media" :size="16" class="gl-vertical-align-text-bottom" />
|
||||
<gl-icon name="media" />
|
||||
</template>
|
||||
</span>
|
||||
<span class="uploading-error-message"></span>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
@import './pages/notes';
|
||||
@import './pages/notifications';
|
||||
@import './pages/pages';
|
||||
@import './pages/pipeline_schedules';
|
||||
@import './pages/pipelines';
|
||||
@import './pages/profile';
|
||||
@import './pages/profiles/preferences';
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
line-height: 28px;
|
||||
white-space: normal;
|
||||
|
||||
/* Small devices (phones, tablets, 768px and lower) */
|
||||
/* Small devices (phones, 768px and lower) */
|
||||
@include media-breakpoint-down(xs) {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
padding: 16px 15px 11px;
|
||||
}
|
||||
|
||||
/* Small devices (phones, tablets, 768px and lower) */
|
||||
/* Small devices (phones, 768px and lower) */
|
||||
@include media-breakpoint-down(sm) {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -102,15 +102,6 @@
|
|||
display: inline-block;
|
||||
text-align: right;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
margin-top: $gl-padding-8;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
> .btn,
|
||||
> .btn-group,
|
||||
> .btn-container,
|
||||
|
|
@ -146,6 +137,35 @@
|
|||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
$controls-margin: $btn-margin-5 - 2px;
|
||||
flex: 0 0 100%;
|
||||
margin-top: $gl-padding-8;
|
||||
|
||||
.controls-item,
|
||||
.controls-item-full,
|
||||
.controls-item:last-child {
|
||||
flex: 1 1 35%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: $controls-margin;
|
||||
|
||||
.btn,
|
||||
.dropdown {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.controls-item-full {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
padding-bottom: 0;
|
||||
width: 100%;
|
||||
|
|
@ -239,32 +259,6 @@
|
|||
pre {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
.nav-controls {
|
||||
$controls-margin: $btn-margin-5 - 2px;
|
||||
flex: 0 0 100%;
|
||||
margin-top: $gl-padding-8;
|
||||
|
||||
.controls-item,
|
||||
.controls-item-full,
|
||||
.controls-item:last-child {
|
||||
flex: 1 1 35%;
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: $controls-margin;
|
||||
|
||||
.btn,
|
||||
.dropdown {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.controls-item-full {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.scrolling-tabs-container {
|
||||
|
|
|
|||
|
|
@ -83,9 +83,6 @@
|
|||
}
|
||||
|
||||
.board {
|
||||
// the next line cannot be replaced with .d-inline-block because it breaks display: none of SortableJS
|
||||
// see https://gitlab.com/gitlab-org/gitlab-foss/issues/64828
|
||||
display: inline-block;
|
||||
width: calc(85vw - 15px);
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
|
|
@ -116,39 +113,10 @@
|
|||
&.is-collapsed {
|
||||
width: 50px;
|
||||
|
||||
.board-title {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.board-title-caret {
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.user-avatar-link,
|
||||
.milestone-icon {
|
||||
margin-top: $gl-padding-8;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.board-title-text {
|
||||
flex-grow: 0;
|
||||
margin: $gl-padding-8 0;
|
||||
|
||||
.board-title-main-text {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.board-title-sub-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.issue-count-badge {
|
||||
border: 0;
|
||||
white-space: nowrap;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.board-title-text > span,
|
||||
.issue-count-badge > span {
|
||||
height: 16px;
|
||||
|
|
@ -197,10 +165,7 @@
|
|||
}
|
||||
|
||||
.board-title {
|
||||
align-items: center;
|
||||
font-size: 1em;
|
||||
border-bottom: 1px solid var(--gray-100, $gray-100);
|
||||
padding: 0 $gl-spacing-scale-3;
|
||||
height: 3rem;
|
||||
|
||||
.js-max-issue-size::before {
|
||||
|
|
@ -208,21 +173,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.board-title-text {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.board-delete.gl-button {
|
||||
background-color: transparent;
|
||||
outline: 0;
|
||||
|
||||
&:hover {
|
||||
color: var(--blue-600, $blue-600);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.board-blank-state,
|
||||
.board-promotion-state {
|
||||
background-color: var(--white, $white);
|
||||
flex: 1;
|
||||
|
|
@ -230,19 +180,6 @@
|
|||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.board-blank-state-list {
|
||||
> li:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.label-color {
|
||||
top: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.board-list-component {
|
||||
min-height: 0; // firefox fix
|
||||
}
|
||||
|
|
@ -311,10 +248,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.board-card-header {
|
||||
text-align: initial;
|
||||
}
|
||||
|
||||
.board-card-assignee {
|
||||
margin-top: -$gl-padding-4;
|
||||
margin-bottom: -$gl-padding-4;
|
||||
|
|
@ -586,28 +519,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.board-swimlanes {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.board-header-collapsed-info-icon:hover {
|
||||
color: var(--gray-900, $gray-900);
|
||||
}
|
||||
|
||||
$epic-icons-spacing: 40px;
|
||||
|
||||
.board-epic-lane {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$epic-icons-spacing});
|
||||
|
||||
.page-with-icon-sidebar & {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$epic-icons-spacing});
|
||||
}
|
||||
|
||||
.page-with-icon-sidebar .is-compact & {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-collapsed-width} - #{$gutter-width} - #{$epic-icons-spacing});
|
||||
}
|
||||
|
||||
.is-compact & {
|
||||
max-width: calc(100vw - #{$contextual-sidebar-width} - #{$gutter-width} - #{$epic-icons-spacing});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@import 'mixins_and_variables_and_functions';
|
||||
|
||||
.pipeline-schedule-form {
|
||||
.gl-field-error {
|
||||
margin: 10px 0 0;
|
||||
|
|
@ -32,11 +34,11 @@
|
|||
}
|
||||
|
||||
.next-run-cell {
|
||||
color: $gl-text-color-secondary;
|
||||
color: var(--gray-500, $gray-500);
|
||||
}
|
||||
|
||||
a {
|
||||
color: $text-color;
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
|
|
@ -46,13 +48,13 @@
|
|||
|
||||
.pipeline-schedules-user-callout {
|
||||
.bordered-box.content-block {
|
||||
border: 1px solid $border-color;
|
||||
border: 1px solid var(--border-color, $border-color);
|
||||
background-color: transparent;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
#dismiss-callout-btn {
|
||||
color: $gl-text-color;
|
||||
color: var(--gl-text-color, $gl-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,6 +193,8 @@ class GroupsController < Groups::ApplicationController
|
|||
protected
|
||||
|
||||
def render_show_html
|
||||
record_experiment_user(:invite_members_empty_group_version_a) if ::Gitlab.com?
|
||||
|
||||
render 'groups/show', locals: { trial: params[:trial] }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Terraform
|
||||
module State
|
||||
class Base < BaseMutation
|
||||
authorize :admin_terraform_state
|
||||
|
||||
argument :id,
|
||||
Types::GlobalIDType[::Terraform::State],
|
||||
required: true,
|
||||
description: 'Global ID of the Terraform state'
|
||||
|
||||
private
|
||||
|
||||
def find_object(id:)
|
||||
GitlabSchema.find_by_gid(id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Terraform
|
||||
module State
|
||||
class Delete < Base
|
||||
graphql_name 'TerraformStateDelete'
|
||||
|
||||
def resolve(id:)
|
||||
state = authorized_find!(id: id)
|
||||
state.destroy
|
||||
|
||||
{ errors: errors_on_object(state) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Terraform
|
||||
module State
|
||||
class Lock < Base
|
||||
graphql_name 'TerraformStateLock'
|
||||
|
||||
def resolve(id:)
|
||||
state = authorized_find!(id: id)
|
||||
|
||||
if state.locked?
|
||||
state.errors.add(:base, 'state is already locked')
|
||||
else
|
||||
state.update(lock_xid: lock_xid, locked_by_user: current_user, locked_at: Time.current)
|
||||
end
|
||||
|
||||
{ errors: errors_on_object(state) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lock_xid
|
||||
SecureRandom.uuid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Terraform
|
||||
module State
|
||||
class Unlock < Base
|
||||
graphql_name 'TerraformStateUnlock'
|
||||
|
||||
def resolve(id:)
|
||||
state = authorized_find!(id: id)
|
||||
state.update(lock_xid: nil, locked_by_user: nil, locked_at: nil)
|
||||
|
||||
{ errors: errors_on_object(state) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
query CanCreateProjectSnippet($fullPath: ID!) {
|
||||
project(fullPath: $fullPath) {
|
||||
__typename
|
||||
userPermissions {
|
||||
__typename
|
||||
createSnippet
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
query GetSnippetQuery($ids: [ID!]) {
|
||||
snippets(ids: $ids) {
|
||||
__typename
|
||||
nodes {
|
||||
__typename
|
||||
id
|
||||
title
|
||||
description
|
||||
descriptionHtml
|
||||
createdAt
|
||||
updatedAt
|
||||
visibilityLevel
|
||||
webUrl
|
||||
httpUrlToRepo
|
||||
sshUrlToRepo
|
||||
blobs {
|
||||
__typename
|
||||
nodes {
|
||||
__typename
|
||||
binary
|
||||
name
|
||||
path
|
||||
rawPath
|
||||
size
|
||||
externalStorage
|
||||
renderedAsText
|
||||
simpleViewer {
|
||||
__typename
|
||||
collapsed
|
||||
renderError
|
||||
tooLarge
|
||||
type
|
||||
fileType
|
||||
}
|
||||
richViewer {
|
||||
__typename
|
||||
collapsed
|
||||
renderError
|
||||
tooLarge
|
||||
type
|
||||
fileType
|
||||
}
|
||||
}
|
||||
}
|
||||
userPermissions {
|
||||
__typename
|
||||
adminSnippet
|
||||
updateSnippet
|
||||
}
|
||||
project {
|
||||
__typename
|
||||
fullPath
|
||||
webUrl
|
||||
}
|
||||
author {
|
||||
__typename
|
||||
id
|
||||
avatarUrl
|
||||
name
|
||||
username
|
||||
webUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +1,13 @@
|
|||
query SnippetBlobContent($ids: [ID!], $rich: Boolean!, $paths: [String!]) {
|
||||
snippets(ids: $ids) {
|
||||
__typename
|
||||
nodes {
|
||||
__typename
|
||||
id
|
||||
blobs(paths: $paths) {
|
||||
__typename
|
||||
nodes {
|
||||
__typename
|
||||
path
|
||||
richData @include(if: $rich)
|
||||
plainData @skip(if: $rich)
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
query CanCreatePersonalSnippet {
|
||||
currentUser {
|
||||
__typename
|
||||
userPermissions {
|
||||
__typename
|
||||
createSnippet
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,9 @@ module Types
|
|||
'If the body of the Note contains only quick actions, the Note will be ' \
|
||||
'destroyed during the update, and no Note will be returned'
|
||||
mount_mutation Mutations::Notes::Destroy
|
||||
mount_mutation Mutations::Terraform::State::Delete
|
||||
mount_mutation Mutations::Terraform::State::Lock
|
||||
mount_mutation Mutations::Terraform::State::Unlock
|
||||
mount_mutation Mutations::Todos::MarkDone
|
||||
mount_mutation Mutations::Todos::Restore
|
||||
mount_mutation Mutations::Todos::MarkAllDone
|
||||
|
|
|
|||
|
|
@ -18,4 +18,8 @@ module InviteMembersHelper
|
|||
experiment_enabled?(:invite_members_version_b) && !can_import_members?
|
||||
end
|
||||
end
|
||||
|
||||
def invite_group_members?(group)
|
||||
experiment_enabled?(:invite_members_empty_group_version_a) && Ability.allowed?(current_user, :admin_group_member, group)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -57,7 +57,10 @@ module PageLayoutHelper
|
|||
|
||||
subject = @project || @user || @group
|
||||
|
||||
image = subject.avatar_url if subject.present?
|
||||
args = {}
|
||||
args[:only_path] = false if Feature.enabled?(:avatar_with_host)
|
||||
|
||||
image = subject.avatar_url(args) if subject.present?
|
||||
image || default
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -12,13 +12,21 @@ module Ci
|
|||
validates :data, json_schema: { filename: "daily_build_group_report_result_data" }
|
||||
|
||||
scope :with_included_projects, -> { includes(:project) }
|
||||
scope :by_projects, -> (ids) { where(project_id: ids) }
|
||||
scope :with_coverage, -> { where("(data->'coverage') IS NOT NULL") }
|
||||
|
||||
def self.upsert_reports(data)
|
||||
upsert_all(data, unique_by: :index_daily_build_group_report_results_unique_columns) if data.any?
|
||||
end
|
||||
store_accessor :data, :coverage
|
||||
|
||||
def self.recent_results(attrs, limit: nil)
|
||||
where(attrs).order(date: :desc, group_name: :asc).limit(limit)
|
||||
class << self
|
||||
def upsert_reports(data)
|
||||
upsert_all(data, unique_by: :index_daily_build_group_report_results_unique_columns) if data.any?
|
||||
end
|
||||
|
||||
def recent_results(attrs, limit: nil)
|
||||
where(attrs).order(date: :desc, group_name: :asc).limit(limit)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Ci::DailyBuildGroupReportResult.prepend_if_ee('EE::Ci::DailyBuildGroupReportResult')
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ module Issuable
|
|||
end
|
||||
end
|
||||
|
||||
def create_wip_note(old_title)
|
||||
def create_draft_note(old_title)
|
||||
return unless issuable.is_a?(MergeRequest)
|
||||
|
||||
if MergeRequest.work_in_progress?(old_title) != issuable.work_in_progress?
|
||||
SystemNoteService.handle_merge_request_wip(issuable, issuable.project, current_user)
|
||||
SystemNoteService.handle_merge_request_draft(issuable, issuable.project, current_user)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ module Issuable
|
|||
end
|
||||
|
||||
def create_title_change_note(old_title)
|
||||
create_wip_note(old_title)
|
||||
create_draft_note(old_title)
|
||||
|
||||
if issuable.wipless_title_changed(old_title)
|
||||
SystemNoteService.change_title(issuable, issuable.project, current_user, old_title)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ module MergeRequests
|
|||
end
|
||||
|
||||
notify_about_push(mr)
|
||||
mark_mr_as_wip_from_commits(mr)
|
||||
mark_mr_as_draft_from_commits(mr)
|
||||
execute_mr_web_hooks(mr)
|
||||
end
|
||||
|
||||
|
|
@ -246,7 +246,7 @@ module MergeRequests
|
|||
notification_service.push_to_merge_request(merge_request, @current_user, new_commits: new_commits, existing_commits: existing_commits)
|
||||
end
|
||||
|
||||
def mark_mr_as_wip_from_commits(merge_request)
|
||||
def mark_mr_as_draft_from_commits(merge_request)
|
||||
return unless @commits.present?
|
||||
|
||||
commit_shas = merge_request.commit_shas
|
||||
|
|
@ -257,7 +257,7 @@ module MergeRequests
|
|||
|
||||
if wip_commit && !merge_request.work_in_progress?
|
||||
merge_request.update(title: merge_request.wip_title)
|
||||
SystemNoteService.add_merge_request_wip_from_commit(
|
||||
SystemNoteService.add_merge_request_draft_from_commit(
|
||||
merge_request,
|
||||
merge_request.project,
|
||||
@current_user,
|
||||
|
|
|
|||
|
|
@ -130,12 +130,12 @@ module SystemNoteService
|
|||
::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).abort_merge_when_pipeline_succeeds(reason)
|
||||
end
|
||||
|
||||
def handle_merge_request_wip(noteable, project, author)
|
||||
::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).handle_merge_request_wip
|
||||
def handle_merge_request_draft(noteable, project, author)
|
||||
::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).handle_merge_request_draft
|
||||
end
|
||||
|
||||
def add_merge_request_wip_from_commit(noteable, project, author, commit)
|
||||
::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).add_merge_request_wip_from_commit(commit)
|
||||
def add_merge_request_draft_from_commit(noteable, project, author, commit)
|
||||
::SystemNotes::MergeRequestsService.new(noteable: noteable, project: project, author: author).add_merge_request_draft_from_commit(commit)
|
||||
end
|
||||
|
||||
def resolve_all_discussions(merge_request, project, author)
|
||||
|
|
|
|||
|
|
@ -26,16 +26,16 @@ module SystemNotes
|
|||
create_note(NoteSummary.new(noteable, project, author, body, action: 'merge'))
|
||||
end
|
||||
|
||||
def handle_merge_request_wip
|
||||
prefix = noteable.work_in_progress? ? "marked" : "unmarked"
|
||||
def handle_merge_request_draft
|
||||
action = noteable.work_in_progress? ? "draft" : "ready"
|
||||
|
||||
body = "#{prefix} as a **Work In Progress**"
|
||||
body = "marked this merge request as **#{action}**"
|
||||
|
||||
create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
|
||||
end
|
||||
|
||||
def add_merge_request_wip_from_commit(commit)
|
||||
body = "marked as a **Work In Progress** from #{commit.to_reference(project)}"
|
||||
def add_merge_request_draft_from_commit(commit)
|
||||
body = "marked this merge request as **draft** from #{commit.to_reference(project)}"
|
||||
|
||||
create_note(NoteSummary.new(noteable, project, author, body, action: 'title'))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,20 +1,30 @@
|
|||
.devops
|
||||
.devops-header
|
||||
%h2.devops-header-title{ class: "devops-#{score_level(@metric.average_percentage_score)}-score" }
|
||||
= number_to_percentage(@metric.average_percentage_score, precision: 1)
|
||||
.devops-header-subtitle
|
||||
= _('DevOps')
|
||||
%br
|
||||
= _('Score')
|
||||
= link_to sprite_icon('question-o', css_class: 'devops-header-icon'), help_page_path('user/admin_area/analytics/dev_ops_report')
|
||||
- usage_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled
|
||||
|
||||
.devops-cards.board-card-container
|
||||
- @metric.cards.each do |card|
|
||||
= render 'card', card: card
|
||||
- if usage_ping_enabled && show_callout?('dev_ops_report_intro_callout_dismissed')
|
||||
= render 'callout'
|
||||
|
||||
.devops-steps.d-none.d-lg-block
|
||||
- @metric.idea_to_production_steps.each_with_index do |step, index|
|
||||
.devops-step{ class: "devops-#{score_level(step.percentage_score)}-score" }
|
||||
= custom_icon("i2p_step_#{index + 1}")
|
||||
%h4.devops-step-title
|
||||
= step.title
|
||||
- if !usage_ping_enabled
|
||||
#js-devops-empty-state{ data: { is_admin: current_user&.admin.to_s, empty_state_svg_path: image_path('illustrations/convdev/convdev_no_index.svg'), enable_usage_ping_link: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), docs_link: help_page_path('development/product_analytics/usage_ping') } }
|
||||
- elsif @metric.blank?
|
||||
= render 'no_data'
|
||||
- else
|
||||
.devops
|
||||
.devops-header
|
||||
%h2.devops-header-title{ class: "devops-#{score_level(@metric.average_percentage_score)}-score" }
|
||||
= number_to_percentage(@metric.average_percentage_score, precision: 1)
|
||||
.devops-header-subtitle
|
||||
= _('DevOps')
|
||||
%br
|
||||
= _('Score')
|
||||
= link_to sprite_icon('question-o', css_class: 'devops-header-icon'), help_page_path('user/admin_area/analytics/dev_ops_report')
|
||||
|
||||
.devops-cards.board-card-container
|
||||
- @metric.cards.each do |card|
|
||||
= render 'card', card: card
|
||||
|
||||
.devops-steps.d-none.d-lg-block
|
||||
- @metric.idea_to_production_steps.each_with_index do |step, index|
|
||||
.devops-step{ class: "devops-#{score_level(step.percentage_score)}-score" }
|
||||
= custom_icon("i2p_step_#{index + 1}")
|
||||
%h4.devops-step-title
|
||||
= step.title
|
||||
|
|
|
|||
|
|
@ -1,15 +1,6 @@
|
|||
- page_title _('DevOps Report')
|
||||
- usage_ping_enabled = Gitlab::CurrentSettings.usage_ping_enabled
|
||||
- add_page_specific_style 'page_bundles/dev_ops_report'
|
||||
|
||||
.container
|
||||
- if usage_ping_enabled && show_callout?('dev_ops_report_intro_callout_dismissed')
|
||||
= render 'callout'
|
||||
|
||||
.gl-mt-3
|
||||
- if !usage_ping_enabled
|
||||
#js-devops-empty-state{ data: { is_admin: current_user&.admin.to_s, empty_state_svg_path: image_path('illustrations/convdev/convdev_no_index.svg'), enable_usage_ping_link: metrics_and_profiling_admin_application_settings_path(anchor: 'js-usage-settings'), docs_link: help_page_path('development/product_analytics/usage_ping') } }
|
||||
- elsif @metric.blank?
|
||||
= render 'no_data'
|
||||
- else
|
||||
= render 'report'
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
= render 'dashboard/groups_head'
|
||||
|
||||
- if params[:filter].blank? && @groups.empty?
|
||||
= render 'shared/groups/empty_state'
|
||||
.empty-state
|
||||
= render 'shared/groups/empty_state'
|
||||
- else
|
||||
= render 'groups'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
- add_to_breadcrumbs _("Schedules"), pipeline_schedules_path(@project)
|
||||
- breadcrumb_title "##{@schedule.id}"
|
||||
- page_title _("Edit"), @schedule.description, _("Pipeline Schedule")
|
||||
- add_page_specific_style 'page_bundles/pipeline_schedules'
|
||||
|
||||
%h3.page-title
|
||||
= _("Edit Pipeline Schedule %{id}") % { id: @schedule.id }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
- breadcrumb_title _("Schedules")
|
||||
|
||||
- page_title _("Pipeline Schedules")
|
||||
- add_page_specific_style 'page_bundles/pipeline_schedules'
|
||||
|
||||
#pipeline-schedules-callout{ data: { docs_url: help_page_path('ci/pipelines/schedules'), image_url: image_path('illustrations/pipeline_schedule_callout.svg') } }
|
||||
.top-area
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
- breadcrumb_title "Schedules"
|
||||
- @breadcrumb_link = namespace_project_pipeline_schedules_path(@project.namespace, @project)
|
||||
- page_title _("New Pipeline Schedule")
|
||||
- add_page_specific_style 'page_bundles/pipeline_schedules'
|
||||
|
||||
- add_to_breadcrumbs("Pipelines", project_pipelines_path(@project))
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
.group-empty-state.row.align-items-center.justify-content-center
|
||||
.icon.text-center.order-md-2
|
||||
.row.gl-align-items-center.gl-justify-content-center
|
||||
.order-md-2
|
||||
= custom_icon("icon_empty_groups")
|
||||
|
||||
.text-content.m-0.order-md-1
|
||||
.text-content.order-md-1{ class: 'gl-m-0!' }
|
||||
%h4= s_("GroupsEmptyState|A group is a collection of several projects.")
|
||||
%p= s_("GroupsEmptyState|If you organize your projects under a group, it works like a folder.")
|
||||
%p= s_("GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group.")
|
||||
- if invite_group_members?(@group)
|
||||
= link_to _('Invite your team'),
|
||||
group_group_members_path(@group),
|
||||
class: 'gl-button btn btn-success-secondary',
|
||||
data: { track_event: 'click_invite_team_group_empty_state', track_label: 'invite_team_group_empty_state' }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
- add_page_startup_graphql_call('snippet/snippet', { ids: [@snippet.to_global_id.uri] })
|
||||
- add_page_startup_graphql_call('snippet/snippet_blob_content', { ids: [@snippet.to_global_id.uri], rich: false, paths: [@snippet.file_name] })
|
||||
- if @snippet.project_id?
|
||||
- add_page_startup_graphql_call('snippet/project_permissions', { fullPath: @snippet.project_id })
|
||||
- else
|
||||
- add_page_startup_graphql_call('snippet/user_permissions')
|
||||
- @hide_top_links = true
|
||||
- @content_class = "limit-container-width limited-inner-width-container" unless fluid_layout
|
||||
- add_to_breadcrumbs _("Snippets"), dashboard_snippets_path
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add GraphQL endpoints to lock, unlock and delete Terraform states
|
||||
merge_request: 43955
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update system note when marking merge request as draft or ready
|
||||
merge_request: 45644
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Pre-fetched GraphQL queries for snippet view
|
||||
merge_request: 46130
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Boards - Fix Milestone icon alignment in header
|
||||
merge_request: 45965
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix dropzone paperclip and loading icons
|
||||
merge_request: 46093
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Refactor secondary_navigation_elements.scss
|
||||
merge_request: 45763
|
||||
author: Takuya Noguchi
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove Cycle Analytics message from en i18n message
|
||||
merge_request: 45178
|
||||
author: Takuya Noguchi
|
||||
type: other
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Create snippet_repository_storage_moves database table
|
||||
merge_request: 45990
|
||||
author:
|
||||
type: added
|
||||
|
|
@ -174,6 +174,7 @@ module Gitlab
|
|||
config.assets.precompile << "notify.css"
|
||||
config.assets.precompile << "mailers/*.css"
|
||||
config.assets.precompile << "page_bundles/_mixins_and_variables_and_functions.css"
|
||||
config.assets.precompile << "page_bundles/alert_management_details.css"
|
||||
config.assets.precompile << "page_bundles/boards.css"
|
||||
config.assets.precompile << "page_bundles/ci_status.css"
|
||||
config.assets.precompile << "page_bundles/cycle_analytics.css"
|
||||
|
|
@ -191,13 +192,13 @@ module Gitlab
|
|||
config.assets.precompile << "page_bundles/milestone.css"
|
||||
config.assets.precompile << "page_bundles/pipeline.css"
|
||||
config.assets.precompile << "page_bundles/pipelines.css"
|
||||
config.assets.precompile << "page_bundles/pipeline_schedules.css"
|
||||
config.assets.precompile << "page_bundles/productivity_analytics.css"
|
||||
config.assets.precompile << "page_bundles/terminal.css"
|
||||
config.assets.precompile << "page_bundles/todos.css"
|
||||
config.assets.precompile << "page_bundles/reports.css"
|
||||
config.assets.precompile << "page_bundles/xterm.css"
|
||||
config.assets.precompile << "page_bundles/wiki.css"
|
||||
config.assets.precompile << "page_bundles/alert_management_details.css"
|
||||
config.assets.precompile << "page_bundles/xterm.css"
|
||||
config.assets.precompile << "lazy_bundles/cropper.css"
|
||||
config.assets.precompile << "performance_bar.css"
|
||||
config.assets.precompile << "lib/ace.js"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
name: avatar_with_host
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45776
|
||||
rollout_issue_url:
|
||||
type: development
|
||||
group: group::editor
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateSnippetRepositoryStorageMove < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
unless table_exists?(:snippet_repository_storage_moves)
|
||||
with_lock_retries do
|
||||
create_table :snippet_repository_storage_moves do |t|
|
||||
t.timestamps_with_timezone
|
||||
t.references :snippet, index: true, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.integer :state, limit: 2, default: 1, null: false
|
||||
t.text :source_storage_name, null: false
|
||||
t.text :destination_storage_name, null: false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
add_text_limit(:snippet_repository_storage_moves, :source_storage_name, 255, constraint_name: 'snippet_repository_storage_moves_source_storage_name')
|
||||
add_text_limit(:snippet_repository_storage_moves, :destination_storage_name, 255, constraint_name: 'snippet_repository_storage_moves_destination_storage_name')
|
||||
end
|
||||
|
||||
def down
|
||||
with_lock_retries do
|
||||
drop_table :snippet_repository_storage_moves
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
f9a573d50f8b4aeb3d8d2cc2f0223ab9970776d663e49e0f022e96158593d929
|
||||
|
|
@ -16146,6 +16146,27 @@ CREATE TABLE snippet_repositories (
|
|||
CONSTRAINT snippet_repositories_verification_failure_text_limit CHECK ((char_length(verification_failure) <= 255))
|
||||
);
|
||||
|
||||
CREATE TABLE snippet_repository_storage_moves (
|
||||
id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
snippet_id bigint NOT NULL,
|
||||
state smallint DEFAULT 1 NOT NULL,
|
||||
source_storage_name text NOT NULL,
|
||||
destination_storage_name text NOT NULL,
|
||||
CONSTRAINT snippet_repository_storage_moves_destination_storage_name CHECK ((char_length(destination_storage_name) <= 255)),
|
||||
CONSTRAINT snippet_repository_storage_moves_source_storage_name CHECK ((char_length(source_storage_name) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE snippet_repository_storage_moves_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE snippet_repository_storage_moves_id_seq OWNED BY snippet_repository_storage_moves.id;
|
||||
|
||||
CREATE TABLE snippet_statistics (
|
||||
snippet_id bigint NOT NULL,
|
||||
repository_size bigint DEFAULT 0 NOT NULL,
|
||||
|
|
@ -18048,6 +18069,8 @@ ALTER TABLE ONLY slack_integrations ALTER COLUMN id SET DEFAULT nextval('slack_i
|
|||
|
||||
ALTER TABLE ONLY smartcard_identities ALTER COLUMN id SET DEFAULT nextval('smartcard_identities_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY snippet_repository_storage_moves ALTER COLUMN id SET DEFAULT nextval('snippet_repository_storage_moves_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY snippet_user_mentions ALTER COLUMN id SET DEFAULT nextval('snippet_user_mentions_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY snippets ALTER COLUMN id SET DEFAULT nextval('snippets_id_seq'::regclass);
|
||||
|
|
@ -19434,6 +19457,9 @@ ALTER TABLE ONLY smartcard_identities
|
|||
ALTER TABLE ONLY snippet_repositories
|
||||
ADD CONSTRAINT snippet_repositories_pkey PRIMARY KEY (snippet_id);
|
||||
|
||||
ALTER TABLE ONLY snippet_repository_storage_moves
|
||||
ADD CONSTRAINT snippet_repository_storage_moves_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY snippet_statistics
|
||||
ADD CONSTRAINT snippet_statistics_pkey PRIMARY KEY (snippet_id);
|
||||
|
||||
|
|
@ -21700,6 +21726,8 @@ CREATE UNIQUE INDEX index_snippet_repositories_on_disk_path ON snippet_repositor
|
|||
|
||||
CREATE INDEX index_snippet_repositories_on_shard_id ON snippet_repositories USING btree (shard_id);
|
||||
|
||||
CREATE INDEX index_snippet_repository_storage_moves_on_snippet_id ON snippet_repository_storage_moves USING btree (snippet_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_snippet_user_mentions_on_note_id ON snippet_user_mentions USING btree (note_id) WHERE (note_id IS NOT NULL);
|
||||
|
||||
CREATE INDEX index_snippets_on_author_id ON snippets USING btree (author_id);
|
||||
|
|
@ -23408,6 +23436,9 @@ ALTER TABLE ONLY ci_pipeline_artifacts
|
|||
ALTER TABLE ONLY group_deletion_schedules
|
||||
ADD CONSTRAINT fk_rails_4b8c694a6c FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY snippet_repository_storage_moves
|
||||
ADD CONSTRAINT fk_rails_4b950f5b94 FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY design_management_designs
|
||||
ADD CONSTRAINT fk_rails_4bb1073360 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
|||
|
|
@ -2515,6 +2515,26 @@ Identifier of Clusters::Cluster
|
|||
"""
|
||||
scalar ClustersClusterID
|
||||
|
||||
"""
|
||||
Represents the code coverage summary for a project
|
||||
"""
|
||||
type CodeCoverageSummary {
|
||||
"""
|
||||
Average percentage of the different code coverage results available for the project.
|
||||
"""
|
||||
averageCoverage: Float
|
||||
|
||||
"""
|
||||
Number of different code coverage results available.
|
||||
"""
|
||||
coverageCount: Int
|
||||
|
||||
"""
|
||||
Latest date when the code coverage was created for the project.
|
||||
"""
|
||||
lastUpdatedAt: Time
|
||||
}
|
||||
|
||||
type Commit {
|
||||
"""
|
||||
Author of the commit
|
||||
|
|
@ -12531,6 +12551,9 @@ type Mutation {
|
|||
removeProjectFromSecurityDashboard(input: RemoveProjectFromSecurityDashboardInput!): RemoveProjectFromSecurityDashboardPayload
|
||||
revertVulnerabilityToDetected(input: RevertVulnerabilityToDetectedInput!): RevertVulnerabilityToDetectedPayload @deprecated(reason: "Use vulnerabilityRevertToDetected. Deprecated in 13.5")
|
||||
runDastScan(input: RunDASTScanInput!): RunDASTScanPayload @deprecated(reason: "Use DastOnDemandScanCreate. Deprecated in 13.4")
|
||||
terraformStateDelete(input: TerraformStateDeleteInput!): TerraformStateDeletePayload
|
||||
terraformStateLock(input: TerraformStateLockInput!): TerraformStateLockPayload
|
||||
terraformStateUnlock(input: TerraformStateUnlockInput!): TerraformStateUnlockPayload
|
||||
todoMarkDone(input: TodoMarkDoneInput!): TodoMarkDonePayload
|
||||
todoRestore(input: TodoRestoreInput!): TodoRestorePayload
|
||||
todoRestoreMany(input: TodoRestoreManyInput!): TodoRestoreManyPayload
|
||||
|
|
@ -13791,6 +13814,12 @@ type Project {
|
|||
last: Int
|
||||
): ClusterAgentConnection
|
||||
|
||||
"""
|
||||
Code coverages summary associated with the project. Available only when
|
||||
feature flag `group_coverage_data_report` is enabled
|
||||
"""
|
||||
codeCoverageSummary: CodeCoverageSummary
|
||||
|
||||
"""
|
||||
Compliance frameworks associated with the project
|
||||
"""
|
||||
|
|
@ -19010,6 +19039,36 @@ type TerraformStateConnection {
|
|||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of TerraformStateDelete
|
||||
"""
|
||||
input TerraformStateDeleteInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Global ID of the Terraform state
|
||||
"""
|
||||
id: TerraformStateID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of TerraformStateDelete
|
||||
"""
|
||||
type TerraformStateDeletePayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Errors encountered during execution of the mutation.
|
||||
"""
|
||||
errors: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
An edge in a connection.
|
||||
"""
|
||||
|
|
@ -19025,6 +19084,71 @@ type TerraformStateEdge {
|
|||
node: TerraformState
|
||||
}
|
||||
|
||||
"""
|
||||
Identifier of Terraform::State
|
||||
"""
|
||||
scalar TerraformStateID
|
||||
|
||||
"""
|
||||
Autogenerated input type of TerraformStateLock
|
||||
"""
|
||||
input TerraformStateLockInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Global ID of the Terraform state
|
||||
"""
|
||||
id: TerraformStateID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of TerraformStateLock
|
||||
"""
|
||||
type TerraformStateLockPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Errors encountered during execution of the mutation.
|
||||
"""
|
||||
errors: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated input type of TerraformStateUnlock
|
||||
"""
|
||||
input TerraformStateUnlockInput {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Global ID of the Terraform state
|
||||
"""
|
||||
id: TerraformStateID!
|
||||
}
|
||||
|
||||
"""
|
||||
Autogenerated return type of TerraformStateUnlock
|
||||
"""
|
||||
type TerraformStateUnlockPayload {
|
||||
"""
|
||||
A unique identifier for the client performing the mutation.
|
||||
"""
|
||||
clientMutationId: String
|
||||
|
||||
"""
|
||||
Errors encountered during execution of the mutation.
|
||||
"""
|
||||
errors: [String!]!
|
||||
}
|
||||
|
||||
"""
|
||||
Represents the Geo sync and verification state of a terraform state version
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -6819,6 +6819,61 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "CodeCoverageSummary",
|
||||
"description": "Represents the code coverage summary for a project",
|
||||
"fields": [
|
||||
{
|
||||
"name": "averageCoverage",
|
||||
"description": "Average percentage of the different code coverage results available for the project.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Float",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "coverageCount",
|
||||
"description": "Number of different code coverage results available.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Int",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "lastUpdatedAt",
|
||||
"description": "Latest date when the code coverage was created for the project.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "Time",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "Commit",
|
||||
|
|
@ -36352,6 +36407,87 @@
|
|||
"isDeprecated": true,
|
||||
"deprecationReason": "Use DastOnDemandScanCreate. Deprecated in 13.4"
|
||||
},
|
||||
{
|
||||
"name": "terraformStateDelete",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TerraformStateDeleteInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateDeletePayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "terraformStateLock",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TerraformStateLockInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateLockPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "terraformStateUnlock",
|
||||
"description": null,
|
||||
"args": [
|
||||
{
|
||||
"name": "input",
|
||||
"description": null,
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TerraformStateUnlockInput",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateUnlockPayload",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "todoMarkDone",
|
||||
"description": null,
|
||||
|
|
@ -40474,6 +40610,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "codeCoverageSummary",
|
||||
"description": "Code coverages summary associated with the project. Available only when feature flag `group_coverage_data_report` is enabled",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "OBJECT",
|
||||
"name": "CodeCoverageSummary",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "complianceFrameworks",
|
||||
"description": "Compliance frameworks associated with the project",
|
||||
|
|
@ -55103,6 +55253,94 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TerraformStateDeleteInput",
|
||||
"description": "Autogenerated input type of TerraformStateDelete",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Global ID of the Terraform state",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "TerraformStateID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateDeletePayload",
|
||||
"description": "Autogenerated return type of TerraformStateDelete",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Errors encountered during execution of the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateEdge",
|
||||
|
|
@ -55148,6 +55386,192 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "TerraformStateID",
|
||||
"description": "Identifier of Terraform::State",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TerraformStateLockInput",
|
||||
"description": "Autogenerated input type of TerraformStateLock",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Global ID of the Terraform state",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "TerraformStateID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateLockPayload",
|
||||
"description": "Autogenerated return type of TerraformStateLock",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Errors encountered during execution of the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "INPUT_OBJECT",
|
||||
"name": "TerraformStateUnlockInput",
|
||||
"description": "Autogenerated input type of TerraformStateUnlock",
|
||||
"fields": null,
|
||||
"inputFields": [
|
||||
{
|
||||
"name": "id",
|
||||
"description": "Global ID of the Terraform state",
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "TerraformStateID",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"defaultValue": null
|
||||
},
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"defaultValue": null
|
||||
}
|
||||
],
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateUnlockPayload",
|
||||
"description": "Autogenerated return type of TerraformStateUnlock",
|
||||
"fields": [
|
||||
{
|
||||
"name": "clientMutationId",
|
||||
"description": "A unique identifier for the client performing the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "errors",
|
||||
"description": "Errors encountered during execution of the mutation.",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "LIST",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"inputFields": null,
|
||||
"interfaces": [
|
||||
|
||||
],
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "TerraformStateVersionRegistry",
|
||||
|
|
|
|||
|
|
@ -390,6 +390,16 @@ Autogenerated return type of ClusterAgentTokenDelete.
|
|||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### CodeCoverageSummary
|
||||
|
||||
Represents the code coverage summary for a project.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `averageCoverage` | Float | Average percentage of the different code coverage results available for the project. |
|
||||
| `coverageCount` | Int | Number of different code coverage results available. |
|
||||
| `lastUpdatedAt` | Time | Latest date when the code coverage was created for the project. |
|
||||
|
||||
### Commit
|
||||
|
||||
| Field | Type | Description |
|
||||
|
|
@ -1984,6 +1994,7 @@ Autogenerated return type of PipelineRetry.
|
|||
| `avatarUrl` | String | URL to avatar image file of the project |
|
||||
| `board` | Board | A single board of the project |
|
||||
| `clusterAgent` | ClusterAgent | Find a single cluster agent by name |
|
||||
| `codeCoverageSummary` | CodeCoverageSummary | Code coverages summary associated with the project. Available only when feature flag `group_coverage_data_report` is enabled |
|
||||
| `containerExpirationPolicy` | ContainerExpirationPolicy | The container expiration policy of the project |
|
||||
| `containerRegistryEnabled` | Boolean | Indicates if the project stores Docker container images in a container registry |
|
||||
| `createdAt` | Time | Timestamp of the project creation |
|
||||
|
|
@ -2629,6 +2640,33 @@ Completion status of tasks.
|
|||
| `name` | String! | Name of the Terraform state |
|
||||
| `updatedAt` | Time! | Timestamp the Terraform state was updated |
|
||||
|
||||
### TerraformStateDeletePayload
|
||||
|
||||
Autogenerated return type of TerraformStateDelete.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### TerraformStateLockPayload
|
||||
|
||||
Autogenerated return type of TerraformStateLock.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### TerraformStateUnlockPayload
|
||||
|
||||
Autogenerated return type of TerraformStateUnlock.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
|
||||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
|
||||
### TerraformStateVersionRegistry
|
||||
|
||||
Represents the Geo sync and verification state of a terraform state version.
|
||||
|
|
|
|||
|
|
@ -577,3 +577,7 @@ Should an error occur during a push, GitLab will display an "Error" highlight fo
|
|||
### 13:Received RST_STREAM with error code 2 with GitHub
|
||||
|
||||
If you receive an "13:Received RST_STREAM with error code 2" while mirroring to a GitHub repository, your GitHub settings might be set to block pushes that expose your email address used in commits. Either set your email address on GitHub to be public, or disable the [Block command line pushes that expose my email](https://github.com/settings/emails) setting.
|
||||
|
||||
### 4:Deadline Exceeded
|
||||
|
||||
When upgrading to GitLab 11.11.8 or newer, a change in how usernames are represented means that you may need to update your mirroring username and password to ensure that `%40` characters are replaced with `@`.
|
||||
|
|
|
|||
|
|
@ -51,6 +51,9 @@ module Gitlab
|
|||
invite_members_version_b: {
|
||||
tracking_category: 'Growth::Expansion::Experiment::InviteMembersVersionB'
|
||||
},
|
||||
invite_members_empty_group_version_a: {
|
||||
tracking_category: 'Growth::Expansion::Experiment::InviteMembersEmptyGroupVersionA'
|
||||
},
|
||||
new_create_project_ui: {
|
||||
tracking_category: 'Manage::Import::Experiment::NewCreateProjectUi'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -305,12 +305,6 @@ msgstr ""
|
|||
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Cycle Analytics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
|
||||
msgstr ""
|
||||
|
||||
msgid "CycleAnalyticsStage|Code"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -483,9 +477,6 @@ msgstr ""
|
|||
msgid "Interval Pattern"
|
||||
msgstr ""
|
||||
|
||||
msgid "Introducing Cycle Analytics"
|
||||
msgstr ""
|
||||
|
||||
msgid "Jobs for last month"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -14425,6 +14425,9 @@ msgstr ""
|
|||
msgid "Invite teammates (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Invite your team"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteEmail|%{inviter} invited you to join the %{project_or_group_name} %{project_or_group} as a %{role}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ module QA
|
|||
it 'allows 2FA code recovery via ssh' do
|
||||
recovery_code = Support::SSH.perform do |ssh|
|
||||
ssh.key = ssh_key
|
||||
ssh.uri = address.gsub(uri.port.to_s, ssh_port)
|
||||
ssh.uri = address.gsub(/(?<=:)(#{uri.port})/, ssh_port)
|
||||
ssh.setup
|
||||
output = ssh.reset_2fa_codes
|
||||
output.scan(/([A-Za-z0-9]{16})\n/).flatten.first
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
FactoryBot.define do
|
||||
factory :ci_daily_build_group_report_result, class: 'Ci::DailyBuildGroupReportResult' do
|
||||
ref_path { Gitlab::Git::BRANCH_REF_PREFIX + 'master' }
|
||||
date { Time.zone.now.to_date }
|
||||
date { Date.current }
|
||||
project
|
||||
last_pipeline factory: :ci_pipeline
|
||||
group_name { 'rspec' }
|
||||
|
|
|
|||
|
|
@ -87,26 +87,22 @@ RSpec.describe "User browses files" do
|
|||
end
|
||||
|
||||
it "shows correct files and links" do
|
||||
# rubocop:disable Lint/Void
|
||||
# Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`.
|
||||
find("a", text: /^empty$/)["href"] == project_tree_url(project, "markdown")
|
||||
find("a", text: /^#id$/)["href"] == project_tree_url(project, "markdown", anchor: "#id")
|
||||
find("a", text: %r{^/#id$})["href"] == project_tree_url(project, "markdown", anchor: "#id")
|
||||
find("a", text: /^README.md#id$/)["href"] == project_blob_url(project, "markdown/README.md", anchor: "#id")
|
||||
find("a", text: %r{^d/README.md#id$})["href"] == project_blob_url(project, "d/markdown/README.md", anchor: "#id")
|
||||
# rubocop:enable Lint/Void
|
||||
|
||||
expect(current_path).to eq(project_tree_path(project, "markdown"))
|
||||
expect(page).to have_content("README.md")
|
||||
.and have_content("CHANGELOG")
|
||||
.and have_content("Welcome to GitLab GitLab is a free project and repository management application")
|
||||
.and have_link("GitLab API doc")
|
||||
.and have_link("GitLab API website")
|
||||
.and have_link("Rake tasks")
|
||||
.and have_link("backup and restore procedure")
|
||||
.and have_link("GitLab API doc directory")
|
||||
.and have_link("Maintenance")
|
||||
.and have_header_with_correct_id_and_link(2, "Application details", "application-details")
|
||||
.and have_content("CHANGELOG")
|
||||
.and have_content("Welcome to GitLab GitLab is a free project and repository management application")
|
||||
.and have_link("GitLab API doc")
|
||||
.and have_link("GitLab API website")
|
||||
.and have_link("Rake tasks")
|
||||
.and have_link("backup and restore procedure")
|
||||
.and have_link("GitLab API doc directory")
|
||||
.and have_link("Maintenance")
|
||||
.and have_header_with_correct_id_and_link(2, "Application details", "application-details")
|
||||
.and have_link("empty", href: "")
|
||||
.and have_link("#id", href: "#id")
|
||||
.and have_link("/#id", href: project_blob_path(project, "markdown/README.md", anchor: "id"))
|
||||
.and have_link("README.md#id", href: project_blob_path(project, "markdown/README.md", anchor: "id"))
|
||||
.and have_link("d/README.md#id", href: project_blob_path(project, "markdown/db/README.md", anchor: "id"))
|
||||
end
|
||||
|
||||
it "shows correct content of file" do
|
||||
|
|
@ -114,10 +110,10 @@ RSpec.describe "User browses files" do
|
|||
|
||||
expect(current_path).to eq(project_blob_path(project, "markdown/doc/api/README.md"))
|
||||
expect(page).to have_content("All API requests require authentication")
|
||||
.and have_content("Contents")
|
||||
.and have_link("Users")
|
||||
.and have_link("Rake tasks")
|
||||
.and have_header_with_correct_id_and_link(1, "GitLab API", "gitlab-api")
|
||||
.and have_content("Contents")
|
||||
.and have_link("Users")
|
||||
.and have_link("Rake tasks")
|
||||
.and have_header_with_correct_id_and_link(1, "GitLab API", "gitlab-api")
|
||||
|
||||
click_link("Users")
|
||||
|
||||
|
|
@ -148,16 +144,13 @@ RSpec.describe "User browses files" do
|
|||
click_link("d")
|
||||
end
|
||||
|
||||
# rubocop:disable Lint/Void
|
||||
# Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`.
|
||||
find("a", text: "..")["href"] == project_tree_url(project, "markdown/d")
|
||||
# rubocop:enable Lint/Void
|
||||
expect(page).to have_link("..", href: project_tree_path(project, "markdown/"))
|
||||
|
||||
page.within(".tree-table") do
|
||||
click_link("README.md")
|
||||
end
|
||||
# Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`.
|
||||
find("a", text: /^empty$/)["href"] == project_blob_url(project, "markdown/d/README.md")
|
||||
|
||||
expect(page).to have_link("empty", href: "")
|
||||
end
|
||||
|
||||
it "shows correct content of directory" do
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ exports[`Snippet Description Edit component rendering matches the snapshot 1`] =
|
|||
class="div-dropzone-hover"
|
||||
>
|
||||
<svg
|
||||
class="div-dropzone-icon"
|
||||
class="div-dropzone-icon s24"
|
||||
>
|
||||
<use
|
||||
xlink:href="undefined#paperclip"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::Terraform::State::Delete do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:state) { create(:terraform_state) }
|
||||
|
||||
let(:mutation) do
|
||||
described_class.new(
|
||||
object: double,
|
||||
context: { current_user: user },
|
||||
field: double
|
||||
)
|
||||
end
|
||||
|
||||
it { expect(described_class.graphql_name).to eq('TerraformStateDelete') }
|
||||
it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) }
|
||||
|
||||
describe '#resolve' do
|
||||
let(:global_id) { state.to_global_id }
|
||||
|
||||
subject { mutation.resolve(id: global_id) }
|
||||
|
||||
context 'user does not have permission' do
|
||||
it 'raises an error', :aggregate_failures do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
expect { state.reload }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has permission' do
|
||||
before do
|
||||
state.project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'deletes the state', :aggregate_failures do
|
||||
expect do
|
||||
expect(subject).to eq(errors: [])
|
||||
end.to change { ::Terraform::State.count }.by(-1)
|
||||
|
||||
expect { state.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:global_id) { user.to_global_id }
|
||||
|
||||
it 'raises an error', :aggregate_failures do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
expect { state.reload }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::Terraform::State::Lock do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:state) { create(:terraform_state) }
|
||||
|
||||
let(:mutation) do
|
||||
described_class.new(
|
||||
object: double,
|
||||
context: { current_user: user },
|
||||
field: double
|
||||
)
|
||||
end
|
||||
|
||||
it { expect(described_class.graphql_name).to eq('TerraformStateLock') }
|
||||
it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) }
|
||||
|
||||
describe '#resolve' do
|
||||
let(:global_id) { state.to_global_id }
|
||||
|
||||
subject { mutation.resolve(id: global_id) }
|
||||
|
||||
context 'user does not have permission' do
|
||||
it 'raises an error', :aggregate_failures do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
expect(state.reload).not_to be_locked
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has permission' do
|
||||
before do
|
||||
state.project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'locks the state', :aggregate_failures do
|
||||
expect(subject).to eq(errors: [])
|
||||
|
||||
expect(state.reload).to be_locked
|
||||
expect(state.locked_by_user).to eq(user)
|
||||
expect(state.lock_xid).to be_present
|
||||
expect(state.locked_at).to be_present
|
||||
end
|
||||
|
||||
context 'state is already locked' do
|
||||
let(:locked_by_user) { create(:user) }
|
||||
let(:state) { create(:terraform_state, :locked, locked_by_user: locked_by_user) }
|
||||
|
||||
it 'does not modify the existing lock', :aggregate_failures do
|
||||
expect(subject).to eq(errors: ['state is already locked'])
|
||||
|
||||
expect(state.reload).to be_locked
|
||||
expect(state.locked_by_user).to eq(locked_by_user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:global_id) { user.to_global_id }
|
||||
|
||||
it 'raises an error', :aggregate_failures do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
expect(state.reload).not_to be_locked
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::Terraform::State::Unlock do
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:state) { create(:terraform_state, :locked) }
|
||||
|
||||
let(:mutation) do
|
||||
described_class.new(
|
||||
object: double,
|
||||
context: { current_user: user },
|
||||
field: double
|
||||
)
|
||||
end
|
||||
|
||||
it { expect(described_class.graphql_name).to eq('TerraformStateUnlock') }
|
||||
it { expect(described_class).to require_graphql_authorizations(:admin_terraform_state) }
|
||||
|
||||
describe '#resolve' do
|
||||
let(:global_id) { state.to_global_id }
|
||||
|
||||
subject { mutation.resolve(id: global_id) }
|
||||
|
||||
context 'user does not have permission' do
|
||||
it 'raises an error', :aggregate_failures do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
expect(state.reload).to be_locked
|
||||
end
|
||||
end
|
||||
|
||||
context 'user has permission' do
|
||||
before do
|
||||
state.project.add_maintainer(user)
|
||||
end
|
||||
|
||||
it 'unlocks the state', :aggregate_failures do
|
||||
expect(subject).to eq(errors: [])
|
||||
expect(state.reload).not_to be_locked
|
||||
end
|
||||
|
||||
context 'state is already unlocked' do
|
||||
let(:state) { create(:terraform_state) }
|
||||
|
||||
it 'does not modify the state' do
|
||||
expect(subject).to eq(errors: [])
|
||||
expect(state.reload).not_to be_locked
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid params' do
|
||||
let(:global_id) { user.to_global_id }
|
||||
|
||||
it 'raises an error', :aggregate_failures do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable)
|
||||
expect(state.reload).to be_locked
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -7,70 +7,110 @@ RSpec.describe InviteMembersHelper do
|
|||
let_it_be(:developer) { create(:user, developer_projects: [project]) }
|
||||
let(:owner) { project.owner }
|
||||
|
||||
before do
|
||||
assign(:project, project)
|
||||
end
|
||||
context 'with project' do
|
||||
before do
|
||||
assign(:project, project)
|
||||
end
|
||||
|
||||
describe "#directly_invite_members?" do
|
||||
context 'when the user is an owner' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { owner }
|
||||
describe "#directly_invite_members?" do
|
||||
context 'when the user is an owner' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { owner }
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_a) { false }
|
||||
|
||||
expect(helper.directly_invite_members?).to eq false
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_a) { true }
|
||||
|
||||
expect(helper.directly_invite_members?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_a) { false }
|
||||
context 'when the user is a developer' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { developer }
|
||||
end
|
||||
|
||||
expect(helper.directly_invite_members?).to eq false
|
||||
end
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_a) { true }
|
||||
|
||||
it 'returns true' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_a) { true }
|
||||
|
||||
expect(helper.directly_invite_members?).to eq true
|
||||
expect(helper.directly_invite_members?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the user is a developer' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { developer }
|
||||
describe "#indirectly_invite_members?" do
|
||||
context 'when a user is a developer' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { developer }
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { false }
|
||||
|
||||
expect(helper.indirectly_invite_members?).to eq false
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { true }
|
||||
|
||||
expect(helper.indirectly_invite_members?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_a) { true }
|
||||
context 'when a user is an owner' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { owner }
|
||||
end
|
||||
|
||||
expect(helper.directly_invite_members?).to eq false
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { true }
|
||||
|
||||
expect(helper.indirectly_invite_members?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#indirectly_invite_members?" do
|
||||
context 'when a user is a developer' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { developer }
|
||||
context 'with group' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
describe "#invite_group_members?" do
|
||||
context 'when the user is an owner' do
|
||||
before do
|
||||
group.add_owner(owner)
|
||||
allow(helper).to receive(:current_user) { owner }
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_empty_group_version_a) { false }
|
||||
|
||||
expect(helper.invite_group_members?(group)).to eq false
|
||||
end
|
||||
|
||||
it 'returns true' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_empty_group_version_a) { true }
|
||||
|
||||
expect(helper.invite_group_members?(group)).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { false }
|
||||
context 'when the user is a developer' do
|
||||
before do
|
||||
group.add_developer(developer)
|
||||
allow(helper).to receive(:current_user) { developer }
|
||||
end
|
||||
|
||||
expect(helper.indirectly_invite_members?).to eq false
|
||||
end
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_empty_group_version_a) { true }
|
||||
|
||||
it 'returns true' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { true }
|
||||
|
||||
expect(helper.indirectly_invite_members?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a user is an owner' do
|
||||
before do
|
||||
allow(helper).to receive(:current_user) { owner }
|
||||
end
|
||||
|
||||
it 'returns false' do
|
||||
allow(helper).to receive(:experiment_enabled?).with(:invite_members_version_b) { true }
|
||||
|
||||
expect(helper.indirectly_invite_members?).to eq false
|
||||
expect(helper.invite_group_members?(group)).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -56,19 +56,24 @@ RSpec.describe PageLayoutHelper do
|
|||
end
|
||||
|
||||
%w(project user group).each do |type|
|
||||
context "with @#{type} assigned" do
|
||||
it "uses #{type.titlecase} avatar if available" do
|
||||
object = double(avatar_url: 'http://example.com/uploads/-/system/avatar.png')
|
||||
assign(type, object)
|
||||
let(:object) { build(type, trait) }
|
||||
let(:trait) { :with_avatar }
|
||||
|
||||
expect(helper.page_image).to eq object.avatar_url
|
||||
context "with @#{type} assigned" do
|
||||
before do
|
||||
assign(type, object)
|
||||
end
|
||||
|
||||
it 'falls back to the default when avatar_url is nil' do
|
||||
object = double(avatar_url: nil)
|
||||
assign(type, object)
|
||||
it "uses #{type.titlecase} avatar full url" do
|
||||
expect(helper.page_image).to eq object.avatar_url(only_path: false)
|
||||
end
|
||||
|
||||
expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png'
|
||||
context 'when avatar_url is nil' do
|
||||
let(:trait) { nil }
|
||||
|
||||
it 'falls back to the default when avatar_url is nil' do
|
||||
expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -77,6 +82,16 @@ RSpec.describe PageLayoutHelper do
|
|||
expect(helper.page_image).to match_asset_path 'assets/gitlab_logo.png'
|
||||
end
|
||||
end
|
||||
|
||||
context 'if avatar_with_host is disabled' do
|
||||
it "#{type.titlecase} does not generate avatar full url" do
|
||||
stub_feature_flags(avatar_with_host: false)
|
||||
|
||||
assign(type, object)
|
||||
|
||||
expect(helper.page_image).to eq object.avatar_url(only_path: true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -81,4 +81,28 @@ RSpec.describe Ci::DailyBuildGroupReportResult do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let(:recent_build_group_report_result) { create(:ci_daily_build_group_report_result, project: project) }
|
||||
let(:old_build_group_report_result) do
|
||||
create(:ci_daily_build_group_report_result, date: 1.week.ago, project: project)
|
||||
end
|
||||
|
||||
describe '.by_projects' do
|
||||
subject { described_class.by_projects([project.id]) }
|
||||
|
||||
it 'returns records by projects' do
|
||||
expect(subject).to contain_exactly(recent_build_group_report_result, old_build_group_report_result)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.with_coverage' do
|
||||
subject { described_class.with_coverage }
|
||||
|
||||
it 'returns data with coverage' do
|
||||
expect(subject).to contain_exactly(recent_build_group_report_result, old_build_group_report_result)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'delete a terraform state' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user, maintainer_projects: [project]) }
|
||||
|
||||
let(:state) { create(:terraform_state, project: project) }
|
||||
let(:mutation) { graphql_mutation(:terraform_state_delete, id: state.to_global_id.to_s) }
|
||||
|
||||
before do
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
end
|
||||
|
||||
include_examples 'a working graphql query'
|
||||
|
||||
it 'deletes the state' do
|
||||
expect { state.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'lock a terraform state' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user, maintainer_projects: [project]) }
|
||||
|
||||
let(:state) { create(:terraform_state, project: project) }
|
||||
let(:mutation) { graphql_mutation(:terraform_state_lock, id: state.to_global_id.to_s) }
|
||||
|
||||
before do
|
||||
expect(state).not_to be_locked
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
end
|
||||
|
||||
include_examples 'a working graphql query'
|
||||
|
||||
it 'locks the state' do
|
||||
expect(state.reload).to be_locked
|
||||
expect(state.locked_by_user).to eq(user)
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'unlock a terraform state' do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:user) { create(:user, maintainer_projects: [project]) }
|
||||
|
||||
let(:state) { create(:terraform_state, :locked, project: project) }
|
||||
let(:mutation) { graphql_mutation(:terraform_state_unlock, id: state.to_global_id.to_s) }
|
||||
|
||||
before do
|
||||
expect(state).to be_locked
|
||||
post_graphql_mutation(mutation, current_user: user)
|
||||
end
|
||||
|
||||
include_examples 'a working graphql query'
|
||||
|
||||
it 'unlocks the state' do
|
||||
expect(state.reload).not_to be_locked
|
||||
end
|
||||
end
|
||||
|
|
@ -36,28 +36,28 @@ RSpec.describe Issuable::CommonSystemNotesService do
|
|||
context 'adding Draft note' do
|
||||
let(:issuable) { create(:merge_request, title: "merge request") }
|
||||
|
||||
it_behaves_like 'system note creation', { title: "Draft: merge request" }, 'marked as a **Work In Progress**'
|
||||
it_behaves_like 'system note creation', { title: "Draft: merge request" }, 'marked this merge request as **draft**'
|
||||
|
||||
context 'and changing title' do
|
||||
before do
|
||||
issuable.update_attribute(:title, "Draft: changed title")
|
||||
end
|
||||
|
||||
it_behaves_like 'draft notes creation', 'marked'
|
||||
it_behaves_like 'draft notes creation', 'draft'
|
||||
end
|
||||
end
|
||||
|
||||
context 'removing Draft note' do
|
||||
let(:issuable) { create(:merge_request, title: "Draft: merge request") }
|
||||
|
||||
it_behaves_like 'system note creation', { title: "merge request" }, 'unmarked as a **Work In Progress**'
|
||||
it_behaves_like 'system note creation', { title: "merge request" }, 'marked this merge request as **ready**'
|
||||
|
||||
context 'and changing title' do
|
||||
before do
|
||||
issuable.update_attribute(:title, "changed title")
|
||||
end
|
||||
|
||||
it_behaves_like 'draft notes creation', 'unmarked'
|
||||
it_behaves_like 'draft notes creation', 'ready'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -683,14 +683,14 @@ RSpec.describe MergeRequests::RefreshService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'marking the merge request as work in progress' do
|
||||
context 'marking the merge request as draft' do
|
||||
let(:refresh_service) { service.new(@project, @user) }
|
||||
|
||||
before do
|
||||
allow(refresh_service).to receive(:execute_hooks)
|
||||
end
|
||||
|
||||
it 'marks the merge request as work in progress from fixup commits' do
|
||||
it 'marks the merge request as draft from fixup commits' do
|
||||
fixup_merge_request = create(:merge_request,
|
||||
source_project: @project,
|
||||
source_branch: 'wip',
|
||||
|
|
@ -705,11 +705,11 @@ RSpec.describe MergeRequests::RefreshService do
|
|||
|
||||
expect(fixup_merge_request.work_in_progress?).to eq(true)
|
||||
expect(fixup_merge_request.notes.last.note).to match(
|
||||
/marked as a \*\*Work In Progress\*\* from #{Commit.reference_pattern}/
|
||||
/marked this merge request as \*\*draft\*\* from #{Commit.reference_pattern}/
|
||||
)
|
||||
end
|
||||
|
||||
it 'references the commit that caused the Work in Progress status' do
|
||||
it 'references the commit that caused the draft status' do
|
||||
wip_merge_request = create(:merge_request,
|
||||
source_project: @project,
|
||||
source_branch: 'wip',
|
||||
|
|
@ -724,11 +724,11 @@ RSpec.describe MergeRequests::RefreshService do
|
|||
refresh_service.execute(oldrev, newrev, 'refs/heads/wip')
|
||||
|
||||
expect(wip_merge_request.reload.notes.last.note).to eq(
|
||||
"marked as a **Work In Progress** from #{wip_commit.id}"
|
||||
"marked this merge request as **draft** from #{wip_commit.id}"
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not mark as WIP based on commits that do not belong to an MR' do
|
||||
it 'does not mark as draft based on commits that do not belong to an MR' do
|
||||
allow(refresh_service).to receive(:find_new_commits)
|
||||
refresh_service.instance_variable_set("@commits", [
|
||||
double(
|
||||
|
|
|
|||
|
|
@ -566,25 +566,25 @@ RSpec.describe SystemNoteService do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.handle_merge_request_wip' do
|
||||
describe '.handle_merge_request_draft' do
|
||||
it 'calls MergeRequestsService' do
|
||||
expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
|
||||
expect(service).to receive(:handle_merge_request_wip)
|
||||
expect(service).to receive(:handle_merge_request_draft)
|
||||
end
|
||||
|
||||
described_class.handle_merge_request_wip(noteable, project, author)
|
||||
described_class.handle_merge_request_draft(noteable, project, author)
|
||||
end
|
||||
end
|
||||
|
||||
describe '.add_merge_request_wip_from_commit' do
|
||||
describe '.add_merge_request_draft_from_commit' do
|
||||
it 'calls MergeRequestsService' do
|
||||
commit = double
|
||||
|
||||
expect_next_instance_of(::SystemNotes::MergeRequestsService) do |service|
|
||||
expect(service).to receive(:add_merge_request_wip_from_commit).with(commit)
|
||||
expect(service).to receive(:add_merge_request_draft_from_commit).with(commit)
|
||||
end
|
||||
|
||||
described_class.add_merge_request_wip_from_commit(noteable, project, author, commit)
|
||||
described_class.add_merge_request_draft_from_commit(noteable, project, author, commit)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -51,44 +51,44 @@ RSpec.describe ::SystemNotes::MergeRequestsService do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.handle_merge_request_wip' do
|
||||
describe '.handle_merge_request_draft' do
|
||||
context 'adding draft note' do
|
||||
let(:noteable) { create(:merge_request, source_project: project, title: 'Draft: Lorem ipsum') }
|
||||
|
||||
subject { service.handle_merge_request_wip }
|
||||
subject { service.handle_merge_request_draft }
|
||||
|
||||
it_behaves_like 'a system note' do
|
||||
let(:action) { 'title' }
|
||||
end
|
||||
|
||||
it 'sets the note text' do
|
||||
expect(subject.note).to eq 'marked as a **Work In Progress**'
|
||||
expect(subject.note).to eq 'marked this merge request as **draft**'
|
||||
end
|
||||
end
|
||||
|
||||
context 'removing wip note' do
|
||||
subject { service.handle_merge_request_wip }
|
||||
context 'removing draft note' do
|
||||
subject { service.handle_merge_request_draft }
|
||||
|
||||
it_behaves_like 'a system note' do
|
||||
let(:action) { 'title' }
|
||||
end
|
||||
|
||||
it 'sets the note text' do
|
||||
expect(subject.note).to eq 'unmarked as a **Work In Progress**'
|
||||
expect(subject.note).to eq 'marked this merge request as **ready**'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.add_merge_request_wip_from_commit' do
|
||||
subject { service.add_merge_request_wip_from_commit(noteable.diff_head_commit) }
|
||||
describe '.add_merge_request_draft_from_commit' do
|
||||
subject { service.add_merge_request_draft_from_commit(noteable.diff_head_commit) }
|
||||
|
||||
it_behaves_like 'a system note' do
|
||||
let(:action) { 'title' }
|
||||
end
|
||||
|
||||
it "posts the 'marked as a Work In Progress from commit' system note" do
|
||||
it "posts the 'marked this merge request as draft from commit' system note" do
|
||||
expect(subject.note).to match(
|
||||
/marked as a \*\*Work In Progress\*\* from #{Commit.reference_pattern}/
|
||||
/marked this merge request as \*\*draft\*\* from #{Commit.reference_pattern}/
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ RSpec.shared_examples 'system note creation' do |update_params, note_text|
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'draft notes creation' do |wip_action|
|
||||
RSpec.shared_examples 'draft notes creation' do |action|
|
||||
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
|
||||
|
||||
it 'creates Draft toggle and title change notes' do
|
||||
expect { subject }.to change { Note.count }.from(0).to(2)
|
||||
|
||||
expect(Note.first.note).to match("#{wip_action} as a **Work In Progress**")
|
||||
expect(Note.first.note).to match("marked this merge request as **#{action}**")
|
||||
expect(Note.second.note).to match('changed title')
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in New Issue