Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2977cf67ec
commit
3b060a68f3
|
@ -1,7 +1,8 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlDatepicker, GlFormInput, GlFormGroup } from '@gitlab/ui';
|
import { GlDatepicker, GlFormGroup } from '@gitlab/ui';
|
||||||
|
|
||||||
import { __ } from '~/locale';
|
import { __ } from '~/locale';
|
||||||
|
import { getDateInFuture } from '~/lib/utils/datetime_utility';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ExpiresAtField',
|
name: 'ExpiresAtField',
|
||||||
|
@ -10,7 +11,6 @@ export default {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
GlDatepicker,
|
GlDatepicker,
|
||||||
GlFormInput,
|
|
||||||
GlFormGroup,
|
GlFormGroup,
|
||||||
MaxExpirationDateMessage: () =>
|
MaxExpirationDateMessage: () =>
|
||||||
import('ee_component/access_tokens/components/max_expiration_date_message.vue'),
|
import('ee_component/access_tokens/components/max_expiration_date_message.vue'),
|
||||||
|
@ -32,20 +32,28 @@ export default {
|
||||||
default: () => null,
|
default: () => null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
in30Days() {
|
||||||
|
const today = new Date();
|
||||||
|
return getDateInFuture(today, 30);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<gl-form-group :label="$options.i18n.label" :label-for="inputAttrs.id">
|
<gl-form-group :label="$options.i18n.label" :label-for="inputAttrs.id">
|
||||||
<gl-datepicker :target="null" :min-date="minDate" :max-date="maxDate">
|
<gl-datepicker
|
||||||
<gl-form-input
|
:target="null"
|
||||||
v-bind="inputAttrs"
|
:min-date="minDate"
|
||||||
class="datepicker gl-datepicker-input"
|
:max-date="maxDate"
|
||||||
autocomplete="off"
|
:default-date="in30Days"
|
||||||
inputmode="none"
|
show-clear-button
|
||||||
data-qa-selector="expiry_date_field"
|
:input-name="inputAttrs.name"
|
||||||
/>
|
:input-id="inputAttrs.id"
|
||||||
</gl-datepicker>
|
:placeholder="inputAttrs.placeholder"
|
||||||
|
data-qa-selector="expiry_date_field"
|
||||||
|
/>
|
||||||
<template #description>
|
<template #description>
|
||||||
<max-expiration-date-message :max-date="maxDate" />
|
<max-expiration-date-message :max-date="maxDate" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -397,6 +397,7 @@ export function relativePathToAbsolute(path, basePath) {
|
||||||
const absolute = isAbsolute(basePath);
|
const absolute = isAbsolute(basePath);
|
||||||
const base = absolute ? basePath : `file:///${basePath}`;
|
const base = absolute ? basePath : `file:///${basePath}`;
|
||||||
const url = new URL(path, base);
|
const url = new URL(path, base);
|
||||||
|
url.pathname = url.pathname.replace(/\/\/+/g, '/');
|
||||||
return absolute ? url.href : decodeURIComponent(url.pathname);
|
return absolute ? url.href : decodeURIComponent(url.pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,8 +208,12 @@ export default {
|
||||||
},
|
},
|
||||||
handleFileDelete(file) {
|
handleFileDelete(file) {
|
||||||
this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
|
this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
|
||||||
this.fileToDelete = { ...file };
|
if (this.packageFiles.length === 1) {
|
||||||
this.$refs.deleteFileModal.show();
|
this.$refs.deleteModal.show();
|
||||||
|
} else {
|
||||||
|
this.fileToDelete = { ...file };
|
||||||
|
this.$refs.deleteFileModal.show();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
confirmFileDelete() {
|
confirmFileDelete() {
|
||||||
this.track(DELETE_PACKAGE_FILE_TRACKING_ACTION);
|
this.track(DELETE_PACKAGE_FILE_TRACKING_ACTION);
|
||||||
|
|
|
@ -38,11 +38,10 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="gl-display-flex gl-pb-4">
|
<div class="gl-display-contents">
|
||||||
<dt class="gl-mr-2">{{ label }}</dt>
|
<dt class="gl-mb-5 gl-mr-6 gl-max-w-26">{{ label }}</dt>
|
||||||
<dd class="gl-mb-0">
|
<dd class="gl-mb-5">
|
||||||
<!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
|
<template v-if="value || $scopedSlots.value">
|
||||||
<template v-if="value || $slots.value">
|
|
||||||
<slot name="value">{{ value }}</slot>
|
<slot name="value">{{ value }}</slot>
|
||||||
</template>
|
</template>
|
||||||
<span v-else class="gl-text-gray-500">{{ emptyValue }}</span>
|
<span v-else class="gl-text-gray-500">{{ emptyValue }}</span>
|
||||||
|
|
|
@ -51,6 +51,9 @@ export default {
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
tagList() {
|
||||||
|
return this.runner.tagList || [];
|
||||||
|
},
|
||||||
isGroupRunner() {
|
isGroupRunner() {
|
||||||
return this.runner?.runnerType === GROUP_TYPE;
|
return this.runner?.runnerType === GROUP_TYPE;
|
||||||
},
|
},
|
||||||
|
@ -66,14 +69,17 @@ export default {
|
||||||
<div>
|
<div>
|
||||||
<runner-upgrade-status-alert class="gl-my-4" :runner="runner" />
|
<runner-upgrade-status-alert class="gl-my-4" :runner="runner" />
|
||||||
<div class="gl-pt-4">
|
<div class="gl-pt-4">
|
||||||
<dl class="gl-mb-0" data-testid="runner-details-list">
|
<dl
|
||||||
|
class="gl-mb-0 gl-display-grid runner-details-grid-template"
|
||||||
|
data-testid="runner-details-list"
|
||||||
|
>
|
||||||
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />
|
<runner-detail :label="s__('Runners|Description')" :value="runner.description" />
|
||||||
<runner-detail
|
<runner-detail
|
||||||
:label="s__('Runners|Last contact')"
|
:label="s__('Runners|Last contact')"
|
||||||
:empty-value="s__('Runners|Never contacted')"
|
:empty-value="s__('Runners|Never contacted')"
|
||||||
>
|
>
|
||||||
<template #value>
|
<template v-if="runner.contactedAt" #value>
|
||||||
<time-ago v-if="runner.contactedAt" :time="runner.contactedAt" />
|
<time-ago :time="runner.contactedAt" />
|
||||||
</template>
|
</template>
|
||||||
</runner-detail>
|
</runner-detail>
|
||||||
<runner-detail :label="s__('Runners|Version')">
|
<runner-detail :label="s__('Runners|Version')">
|
||||||
|
@ -87,8 +93,8 @@ export default {
|
||||||
<runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" />
|
<runner-detail :label="s__('Runners|Architecture')" :value="runner.architectureName" />
|
||||||
<runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" />
|
<runner-detail :label="s__('Runners|Platform')" :value="runner.platformName" />
|
||||||
<runner-detail :label="s__('Runners|Configuration')">
|
<runner-detail :label="s__('Runners|Configuration')">
|
||||||
<template #value>
|
<template v-if="configTextProtected || configTextUntagged" #value>
|
||||||
<gl-intersperse v-if="configTextProtected || configTextUntagged">
|
<gl-intersperse>
|
||||||
<span v-if="configTextProtected">{{ configTextProtected }}</span>
|
<span v-if="configTextProtected">{{ configTextProtected }}</span>
|
||||||
<span v-if="configTextUntagged">{{ configTextUntagged }}</span>
|
<span v-if="configTextUntagged">{{ configTextUntagged }}</span>
|
||||||
</gl-intersperse>
|
</gl-intersperse>
|
||||||
|
@ -96,13 +102,8 @@ export default {
|
||||||
</runner-detail>
|
</runner-detail>
|
||||||
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
|
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
|
||||||
<runner-detail :label="s__('Runners|Tags')">
|
<runner-detail :label="s__('Runners|Tags')">
|
||||||
<template #value>
|
<template v-if="tagList.length" #value>
|
||||||
<runner-tags
|
<runner-tags class="gl-vertical-align-middle" :tag-list="tagList" size="sm" />
|
||||||
v-if="runner.tagList && runner.tagList.length"
|
|
||||||
class="gl-vertical-align-middle"
|
|
||||||
:tag-list="runner.tagList"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</runner-detail>
|
</runner-detail>
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,9 @@ export default {
|
||||||
signedIn() {
|
signedIn() {
|
||||||
return this.currentUser.username !== undefined;
|
return this.currentUser.username !== undefined;
|
||||||
},
|
},
|
||||||
|
issuableAuthor() {
|
||||||
|
return this.issuable?.author;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
iid(_, oldIid) {
|
iid(_, oldIid) {
|
||||||
|
@ -266,6 +269,7 @@ export default {
|
||||||
:current-user="currentUser"
|
:current-user="currentUser"
|
||||||
:issuable-type="issuableType"
|
:issuable-type="issuableType"
|
||||||
:is-editing="edit"
|
:is-editing="edit"
|
||||||
|
:issuable-author="issuableAuthor"
|
||||||
class="gl-w-full dropdown-menu-user gl-mt-n3"
|
class="gl-w-full dropdown-menu-user gl-mt-n3"
|
||||||
@toggle="collapseWidget"
|
@toggle="collapseWidget"
|
||||||
@error="showError"
|
@error="showError"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlAvatarLabeled, GlAvatarLink, GlIcon } from '@gitlab/ui';
|
import { GlAvatarLabeled, GlIcon } from '@gitlab/ui';
|
||||||
import { IssuableType } from '~/issues/constants';
|
import { IssuableType } from '~/issues/constants';
|
||||||
import { s__, sprintf } from '~/locale';
|
import { s__, sprintf } from '~/locale';
|
||||||
|
|
||||||
|
@ -11,7 +11,6 @@ const AVAILABILITY_STATUS = {
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
GlAvatarLabeled,
|
GlAvatarLabeled,
|
||||||
GlAvatarLink,
|
|
||||||
GlIcon,
|
GlIcon,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -47,23 +46,21 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<gl-avatar-link>
|
<gl-avatar-labeled
|
||||||
<gl-avatar-labeled
|
:size="32"
|
||||||
:size="32"
|
:label="userLabel"
|
||||||
:label="userLabel"
|
:sub-label="`@${user.username}`"
|
||||||
:sub-label="`@${user.username}`"
|
:src="user.avatarUrl || user.avatar || user.avatar_url"
|
||||||
:src="user.avatarUrl || user.avatar || user.avatar_url"
|
class="gl-align-items-center gl-relative sidebar-participant"
|
||||||
class="gl-align-items-center gl-relative"
|
>
|
||||||
>
|
<template #meta>
|
||||||
<template #meta>
|
<gl-icon
|
||||||
<gl-icon
|
v-if="hasCannotMergeIcon"
|
||||||
v-if="hasCannotMergeIcon"
|
name="warning-solid"
|
||||||
name="warning-solid"
|
aria-hidden="true"
|
||||||
aria-hidden="true"
|
class="merge-icon gl-left-6 gl-bottom-0"
|
||||||
class="merge-icon"
|
:size="12"
|
||||||
:size="12"
|
/>
|
||||||
/>
|
</template>
|
||||||
</template>
|
</gl-avatar-labeled>
|
||||||
</gl-avatar-labeled>
|
|
||||||
</gl-avatar-link>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -8,6 +8,10 @@ query issueAssignees($fullPath: ID!, $iid: String!) {
|
||||||
issuable: issue(iid: $iid) {
|
issuable: issue(iid: $iid) {
|
||||||
__typename
|
__typename
|
||||||
id
|
id
|
||||||
|
author {
|
||||||
|
...User
|
||||||
|
...UserAvailability
|
||||||
|
}
|
||||||
assignees {
|
assignees {
|
||||||
nodes {
|
nodes {
|
||||||
...User
|
...User
|
||||||
|
|
|
@ -6,6 +6,13 @@ query getMrAssignees($fullPath: ID!, $iid: String!) {
|
||||||
id
|
id
|
||||||
issuable: mergeRequest(iid: $iid) {
|
issuable: mergeRequest(iid: $iid) {
|
||||||
id
|
id
|
||||||
|
author {
|
||||||
|
...User
|
||||||
|
...UserAvailability
|
||||||
|
mergeRequestInteraction {
|
||||||
|
canMerge
|
||||||
|
}
|
||||||
|
}
|
||||||
assignees {
|
assignees {
|
||||||
nodes {
|
nodes {
|
||||||
...User
|
...User
|
||||||
|
|
|
@ -77,6 +77,11 @@ export default {
|
||||||
required: false,
|
required: false,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
issuableAuthor: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -178,7 +183,7 @@ export default {
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.moveCurrentUserToStart(mergedSearchResults);
|
return this.moveCurrentUserAndAuthorToStart(mergedSearchResults);
|
||||||
},
|
},
|
||||||
isSearchEmpty() {
|
isSearchEmpty() {
|
||||||
return this.search === '';
|
return this.search === '';
|
||||||
|
@ -196,14 +201,21 @@ export default {
|
||||||
showCurrentUser() {
|
showCurrentUser() {
|
||||||
return this.currentUser.username && !this.isCurrentUserInList && this.isSearchEmpty;
|
return this.currentUser.username && !this.isCurrentUserInList && this.isSearchEmpty;
|
||||||
},
|
},
|
||||||
|
showAuthor() {
|
||||||
|
return (
|
||||||
|
this.issuableAuthor &&
|
||||||
|
!this.users.some((user) => user.id === this.issuableAuthor.id) &&
|
||||||
|
this.isSearchEmpty
|
||||||
|
);
|
||||||
|
},
|
||||||
selectedFiltered() {
|
selectedFiltered() {
|
||||||
if (this.shouldShowParticipants) {
|
if (this.shouldShowParticipants) {
|
||||||
return this.moveCurrentUserToStart(this.value);
|
return this.moveCurrentUserAndAuthorToStart(this.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundUsernames = this.users.map(({ username }) => username);
|
const foundUsernames = this.users.map(({ username }) => username);
|
||||||
const filtered = this.value.filter(({ username }) => foundUsernames.includes(username));
|
const filtered = this.value.filter(({ username }) => foundUsernames.includes(username));
|
||||||
return this.moveCurrentUserToStart(filtered);
|
return this.moveCurrentUserAndAuthorToStart(filtered);
|
||||||
},
|
},
|
||||||
selectedUserNames() {
|
selectedUserNames() {
|
||||||
return this.value.map(({ username }) => username);
|
return this.value.map(({ username }) => username);
|
||||||
|
@ -254,20 +266,22 @@ export default {
|
||||||
showDivider(list) {
|
showDivider(list) {
|
||||||
return list.length > 0 && this.isSearchEmpty;
|
return list.length > 0 && this.isSearchEmpty;
|
||||||
},
|
},
|
||||||
moveCurrentUserToStart(users) {
|
moveCurrentUserAndAuthorToStart(users = []) {
|
||||||
if (!users) {
|
let sortedUsers = [...users];
|
||||||
return [];
|
|
||||||
|
const author = sortedUsers.find((user) => user.id === this.issuableAuthor?.id);
|
||||||
|
if (author) {
|
||||||
|
sortedUsers = [author, ...sortedUsers.filter((user) => user.id !== author.id)];
|
||||||
}
|
}
|
||||||
const usersCopy = [...users];
|
|
||||||
const currentUser = usersCopy.find((user) => user.username === this.currentUser.username);
|
const currentUser = sortedUsers.find((user) => user.username === this.currentUser.username);
|
||||||
|
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
currentUser.canMerge = this.currentUser.canMerge;
|
currentUser.canMerge = this.currentUser.canMerge;
|
||||||
const index = usersCopy.indexOf(currentUser);
|
sortedUsers = [currentUser, ...sortedUsers.filter((user) => user.id !== currentUser.id)];
|
||||||
usersCopy.splice(0, 0, usersCopy.splice(index, 1)[0]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return usersCopy;
|
return sortedUsers;
|
||||||
},
|
},
|
||||||
setSearchKey(value) {
|
setSearchKey(value) {
|
||||||
this.search = value.trim();
|
this.search = value.trim();
|
||||||
|
@ -298,7 +312,7 @@ export default {
|
||||||
<gl-loading-icon
|
<gl-loading-icon
|
||||||
v-if="isLoading"
|
v-if="isLoading"
|
||||||
data-testid="loading-participants"
|
data-testid="loading-participants"
|
||||||
size="lg"
|
size="md"
|
||||||
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
|
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
@ -312,8 +326,8 @@ export default {
|
||||||
>
|
>
|
||||||
<span :class="selectedIsEmpty ? 'gl-pl-0' : 'gl-pl-6'" class="gl-font-weight-bold">{{
|
<span :class="selectedIsEmpty ? 'gl-pl-0' : 'gl-pl-6'" class="gl-font-weight-bold">{{
|
||||||
$options.i18n.unassigned
|
$options.i18n.unassigned
|
||||||
}}</span></gl-dropdown-item
|
}}</span>
|
||||||
>
|
</gl-dropdown-item>
|
||||||
</template>
|
</template>
|
||||||
<gl-dropdown-divider v-if="showDivider(selectedFiltered)" />
|
<gl-dropdown-divider v-if="showDivider(selectedFiltered)" />
|
||||||
<gl-dropdown-item
|
<gl-dropdown-item
|
||||||
|
@ -342,7 +356,17 @@ export default {
|
||||||
/>
|
/>
|
||||||
</gl-dropdown-item>
|
</gl-dropdown-item>
|
||||||
</template>
|
</template>
|
||||||
<gl-dropdown-divider v-if="showDivider(unselectedFiltered)" />
|
<gl-dropdown-item
|
||||||
|
v-if="showAuthor"
|
||||||
|
data-testid="issuable-author"
|
||||||
|
@click.native.capture.stop="selectAssignee(issuableAuthor)"
|
||||||
|
>
|
||||||
|
<sidebar-participant
|
||||||
|
:user="issuableAuthor"
|
||||||
|
:issuable-type="issuableType"
|
||||||
|
class="gl-pl-6!"
|
||||||
|
/>
|
||||||
|
</gl-dropdown-item>
|
||||||
<gl-dropdown-item
|
<gl-dropdown-item
|
||||||
v-for="unselectedUser in unselectedFiltered"
|
v-for="unselectedUser in unselectedFiltered"
|
||||||
:key="unselectedUser.id"
|
:key="unselectedUser.id"
|
||||||
|
|
|
@ -477,6 +477,12 @@
|
||||||
height: 2 * $gl-padding;
|
height: 2 * $gl-padding;
|
||||||
margin: 0 10px 0 0;
|
margin: 0 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-participant {
|
||||||
|
.merge-icon {
|
||||||
|
top: calc(50% + 5px);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu-user-full-name {
|
.dropdown-menu-user-full-name {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
.runner-details-grid-template {
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
|
@ -69,10 +69,15 @@ module Ci
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_by_upgrade_status!
|
def filter_by_upgrade_status!
|
||||||
return unless @params.key?(:upgrade_status)
|
upgrade_status = @params[:upgrade_status]
|
||||||
return unless Ci::RunnerVersion.statuses.key?(@params[:upgrade_status])
|
|
||||||
|
|
||||||
@runners = @runners.with_upgrade_status(@params[:upgrade_status])
|
return unless upgrade_status
|
||||||
|
|
||||||
|
unless Ci::RunnerVersion.statuses.key?(upgrade_status)
|
||||||
|
raise ArgumentError, "Invalid upgrade status value '#{upgrade_status}'"
|
||||||
|
end
|
||||||
|
|
||||||
|
@runners = @runners.with_upgrade_status(upgrade_status)
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_by_runner_type!
|
def filter_by_runner_type!
|
||||||
|
|
|
@ -8,7 +8,6 @@ module Ci
|
||||||
enum_with_nil status: {
|
enum_with_nil status: {
|
||||||
not_processed: nil,
|
not_processed: nil,
|
||||||
invalid_version: -1,
|
invalid_version: -1,
|
||||||
unknown: 0,
|
|
||||||
not_available: 1,
|
not_available: 1,
|
||||||
available: 2,
|
available: 2,
|
||||||
recommended: 3
|
recommended: 3
|
||||||
|
@ -16,7 +15,6 @@ module Ci
|
||||||
|
|
||||||
STATUS_DESCRIPTIONS = {
|
STATUS_DESCRIPTIONS = {
|
||||||
invalid_version: 'Runner version is not valid.',
|
invalid_version: 'Runner version is not valid.',
|
||||||
unknown: 'Upgrade status is unknown.',
|
|
||||||
not_available: 'Upgrade is not available for the runner.',
|
not_available: 'Upgrade is not available for the runner.',
|
||||||
available: 'Upgrade is available for the runner.',
|
available: 'Upgrade is available for the runner.',
|
||||||
recommended: 'Upgrade is available and recommended for the runner.'
|
recommended: 'Upgrade is available and recommended for the runner.'
|
||||||
|
@ -27,7 +25,7 @@ module Ci
|
||||||
|
|
||||||
# This scope returns all versions that might need recalculating. For instance, once a version is considered
|
# This scope returns all versions that might need recalculating. For instance, once a version is considered
|
||||||
# :recommended, it normally doesn't change status even if the instance is upgraded
|
# :recommended, it normally doesn't change status even if the instance is upgraded
|
||||||
scope :potentially_outdated, -> { where(status: [nil, :not_available, :available, :unknown]) }
|
scope :potentially_outdated, -> { where(status: [nil, :not_available, :available]) }
|
||||||
|
|
||||||
validates :version, length: { maximum: 2048 }
|
validates :version, length: { maximum: 2048 }
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ module Integrations
|
||||||
validates :external_wiki_url, presence: true, public_url: true, if: :activated?
|
validates :external_wiki_url, presence: true, public_url: true, if: :activated?
|
||||||
|
|
||||||
field :external_wiki_url,
|
field :external_wiki_url,
|
||||||
|
section: SECTION_TYPE_CONNECTION,
|
||||||
title: -> { s_('ExternalWikiService|External wiki URL') },
|
title: -> { s_('ExternalWikiService|External wiki URL') },
|
||||||
placeholder: -> { s_('ExternalWikiService|https://example.com/xxx/wiki/...') },
|
placeholder: -> { s_('ExternalWikiService|https://example.com/xxx/wiki/...') },
|
||||||
help: -> { s_('ExternalWikiService|Enter the URL to the external wiki.') },
|
help: -> { s_('ExternalWikiService|Enter the URL to the external wiki.') },
|
||||||
|
@ -28,6 +29,16 @@ module Integrations
|
||||||
s_('Link an external wiki from the project\'s sidebar. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
|
s_('Link an external wiki from the project\'s sidebar. %{docs_link}').html_safe % { docs_link: docs_link.html_safe }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sections
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: SECTION_TYPE_CONNECTION,
|
||||||
|
title: s_('Integrations|Connection details'),
|
||||||
|
description: help
|
||||||
|
}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
def execute(_data)
|
def execute(_data)
|
||||||
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
|
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
|
||||||
response.body if response.code == 200
|
response.body if response.code == 200
|
||||||
|
|
|
@ -28,6 +28,7 @@ class Member < ApplicationRecord
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
belongs_to :source, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations
|
||||||
belongs_to :member_namespace, inverse_of: :namespace_members, foreign_key: 'member_namespace_id', class_name: 'Namespace'
|
belongs_to :member_namespace, inverse_of: :namespace_members, foreign_key: 'member_namespace_id', class_name: 'Namespace'
|
||||||
|
belongs_to :member_role
|
||||||
has_one :member_task
|
has_one :member_task
|
||||||
|
|
||||||
delegate :name, :username, :email, :last_activity_on, to: :user, prefix: true
|
delegate :name, :username, :email, :last_activity_on, to: :user, prefix: true
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MemberRole < ApplicationRecord # rubocop:disable Gitlab/NamespacedClass
|
||||||
|
has_many :members
|
||||||
|
belongs_to :namespace
|
||||||
|
|
||||||
|
validates :namespace_id, presence: true
|
||||||
|
validates :base_access_level, presence: true
|
||||||
|
end
|
|
@ -49,6 +49,7 @@ class Namespace < ApplicationRecord
|
||||||
has_one :namespace_statistics
|
has_one :namespace_statistics
|
||||||
has_one :namespace_route, foreign_key: :namespace_id, autosave: false, inverse_of: :namespace, class_name: 'Route'
|
has_one :namespace_route, foreign_key: :namespace_id, autosave: false, inverse_of: :namespace, class_name: 'Route'
|
||||||
has_many :namespace_members, foreign_key: :member_namespace_id, inverse_of: :member_namespace, class_name: 'Member'
|
has_many :namespace_members, foreign_key: :member_namespace_id, inverse_of: :member_namespace, class_name: 'Member'
|
||||||
|
has_many :member_roles
|
||||||
|
|
||||||
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
|
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
|
||||||
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
|
has_many :runners, through: :runner_namespaces, source: :runner, class_name: 'Ci::Runner'
|
||||||
|
|
|
@ -34,7 +34,7 @@ module IncidentManagement
|
||||||
attr_reader :timeline_event, :incident, :user, :note, :occurred_at
|
attr_reader :timeline_event, :incident, :user, :note, :occurred_at
|
||||||
|
|
||||||
def update_params
|
def update_params
|
||||||
{ updated_by_user: user, note: note.presence, occurred_at: occurred_at.presence }.compact
|
{ updated_by_user: user, note: note, occurred_at: occurred_at }.compact
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_system_note(timeline_event)
|
def add_system_note(timeline_event)
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
= link_to(s_('AdminArea|New user'), new_admin_user_path, class: "btn gl-button btn-default")
|
= link_to(s_('AdminArea|New user'), new_admin_user_path, class: "btn gl-button btn-default")
|
||||||
= c.footer do
|
= c.footer do
|
||||||
.d-flex.align-items-center
|
.d-flex.align-items-center
|
||||||
= link_to(s_('AdminArea|View latest users'), admin_users_path)
|
= link_to(s_('AdminArea|View latest users'), admin_users_path({ sort: 'created_desc' }))
|
||||||
= sprite_icon('chevron-right', size: 12, css_class: 'gl-text-gray-700 gl-ml-2')
|
= sprite_icon('chevron-right', size: 12, css_class: 'gl-text-gray-700 gl-ml-2')
|
||||||
.col-md-4.gl-mb-6
|
.col-md-4.gl-mb-6
|
||||||
= render Pajamas::CardComponent.new(**component_params) do |c|
|
= render Pajamas::CardComponent.new(**component_params) do |c|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
- add_page_specific_style 'page_bundles/ci_status'
|
- add_page_specific_style 'page_bundles/ci_status'
|
||||||
|
- add_page_specific_style 'page_bundles/runner_details'
|
||||||
|
|
||||||
- title = "##{@runner.id} (#{@runner.short_sha})"
|
- title = "##{@runner.id} (#{@runner.short_sha})"
|
||||||
- breadcrumb_title title
|
- breadcrumb_title title
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
- add_page_specific_style 'page_bundles/runner_details'
|
||||||
|
|
||||||
- add_to_breadcrumbs _('Runners'), group_runners_path(@group)
|
- add_to_breadcrumbs _('Runners'), group_runners_path(@group)
|
||||||
|
|
||||||
- if Feature.enabled?(:group_runner_view_ui, @group)
|
- if Feature.enabled?(:group_runner_view_ui, @group)
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
token: @resource_access_token,
|
token: @resource_access_token,
|
||||||
scopes: @scopes,
|
scopes: @scopes,
|
||||||
access_levels: GroupMember.access_level_roles,
|
access_levels: GroupMember.access_level_roles,
|
||||||
default_access_level: Gitlab::Access::MAINTAINER,
|
default_access_level: Gitlab::Access::GUEST,
|
||||||
prefix: :resource_access_token,
|
prefix: :resource_access_token,
|
||||||
help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
|
help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
.card-body
|
.card-body
|
||||||
%p.gl-mb-0
|
%p.gl-mb-0
|
||||||
- docs_link_start = "<a href='#{help_page_path('user/project/pages/index')}' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_docs_link'>".html_safe
|
- docs_link_start = "<a href='#{help_page_path('user/project/pages/index')}' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_docs_link'>".html_safe
|
||||||
- samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_samples_link>"
|
- samples_link_start = "<a href='https://gitlab.com/pages' target='_blank' rel='noopener noreferrer' data-track-action='click_link' data-track-label='pages_samples_link'>".html_safe
|
||||||
.html_safe
|
|
||||||
- link_end = '</a>'.html_safe
|
- link_end = '</a>'.html_safe
|
||||||
= s_('GitLabPages|Your Pages site is not configured yet. See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also take some inspiration from the %{samples_link_start}sample Pages projects%{link_end}.').html_safe % { docs_link_start: docs_link_start, samples_link_start: samples_link_start, link_end: link_end }
|
= s_('GitLabPages|Your Pages site is not configured yet. See the %{docs_link_start}GitLab Pages documentation%{link_end} to learn how to upload your static site and have GitLab serve it. You can also take some inspiration from the %{samples_link_start}sample Pages projects%{link_end}.').html_safe % { docs_link_start: docs_link_start, samples_link_start: samples_link_start, link_end: link_end }
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
token: @resource_access_token,
|
token: @resource_access_token,
|
||||||
scopes: @scopes,
|
scopes: @scopes,
|
||||||
access_levels: ProjectMember.permissible_access_level_roles(current_user, @project),
|
access_levels: ProjectMember.permissible_access_level_roles(current_user, @project),
|
||||||
default_access_level: Gitlab::Access::MAINTAINER,
|
default_access_level: Gitlab::Access::GUEST,
|
||||||
prefix: :resource_access_token,
|
prefix: :resource_access_token,
|
||||||
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
|
help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
|
||||||
|
|
||||||
|
|
|
@ -297,6 +297,7 @@ module Gitlab
|
||||||
config.assets.precompile << "page_bundles/projects_edit.css"
|
config.assets.precompile << "page_bundles/projects_edit.css"
|
||||||
config.assets.precompile << "page_bundles/reports.css"
|
config.assets.precompile << "page_bundles/reports.css"
|
||||||
config.assets.precompile << "page_bundles/roadmap.css"
|
config.assets.precompile << "page_bundles/roadmap.css"
|
||||||
|
config.assets.precompile << "page_bundles/runner_details.css"
|
||||||
config.assets.precompile << "page_bundles/security_dashboard.css"
|
config.assets.precompile << "page_bundles/security_dashboard.css"
|
||||||
config.assets.precompile << "page_bundles/security_discover.css"
|
config.assets.precompile << "page_bundles/security_discover.css"
|
||||||
config.assets.precompile << "page_bundles/signup.css"
|
config.assets.precompile << "page_bundles/signup.css"
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
table_name: member_roles
|
||||||
|
classes:
|
||||||
|
- MemberRole
|
||||||
|
feature_categories:
|
||||||
|
- projects
|
||||||
|
- subgroups
|
||||||
|
description: Stores custom roles with composable permissions
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92152
|
||||||
|
milestone: '15.2'
|
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateMemberRoles < Gitlab::Database::Migration[2.0]
|
||||||
|
def change
|
||||||
|
create_table :member_roles do |t|
|
||||||
|
t.references :namespace,
|
||||||
|
index: true,
|
||||||
|
null: false,
|
||||||
|
foreign_key: { on_delete: :cascade }
|
||||||
|
t.timestamps_with_timezone null: false
|
||||||
|
t.integer :base_access_level, null: false
|
||||||
|
t.boolean :download_code, default: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddMemberRoleIdToMembers < Gitlab::Database::Migration[2.0]
|
||||||
|
enable_lock_retries!
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_column :members, :member_role_id, :bigint
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
remove_column :members, :member_role_id
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,19 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddMemberRolesRelationToMembers < Gitlab::Database::Migration[2.0]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
INDEX_NAME = 'index_members_on_member_role_id'
|
||||||
|
|
||||||
|
def up
|
||||||
|
add_concurrent_index :members, :member_role_id, name: INDEX_NAME
|
||||||
|
add_concurrent_foreign_key :members, :member_roles, column: :member_role_id, on_delete: :cascade
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
with_lock_retries do
|
||||||
|
remove_foreign_key :members, column: :member_role_id
|
||||||
|
end
|
||||||
|
|
||||||
|
remove_concurrent_index_by_name :members, INDEX_NAME
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1 @@
|
||||||
|
78bb335a94237bfb5c5401807c9fc5e8ff9ec331af0ca0d3c5626253af5cde3f
|
|
@ -0,0 +1 @@
|
||||||
|
179c400efd7d31b78b4314104c5e9cbdf744c5e2966cecf724d1b7088b515fd1
|
|
@ -0,0 +1 @@
|
||||||
|
431392f5f88f493371b77263bbe380d08e486e8ba0d013213e6fe7fdcda3c7db
|
|
@ -16996,6 +16996,24 @@ CREATE SEQUENCE loose_foreign_keys_deleted_records_id_seq
|
||||||
|
|
||||||
ALTER SEQUENCE loose_foreign_keys_deleted_records_id_seq OWNED BY loose_foreign_keys_deleted_records.id;
|
ALTER SEQUENCE loose_foreign_keys_deleted_records_id_seq OWNED BY loose_foreign_keys_deleted_records.id;
|
||||||
|
|
||||||
|
CREATE TABLE member_roles (
|
||||||
|
id bigint NOT NULL,
|
||||||
|
namespace_id bigint NOT NULL,
|
||||||
|
created_at timestamp with time zone NOT NULL,
|
||||||
|
updated_at timestamp with time zone NOT NULL,
|
||||||
|
base_access_level integer NOT NULL,
|
||||||
|
download_code boolean DEFAULT false
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE SEQUENCE member_roles_id_seq
|
||||||
|
START WITH 1
|
||||||
|
INCREMENT BY 1
|
||||||
|
NO MINVALUE
|
||||||
|
NO MAXVALUE
|
||||||
|
CACHE 1;
|
||||||
|
|
||||||
|
ALTER SEQUENCE member_roles_id_seq OWNED BY member_roles.id;
|
||||||
|
|
||||||
CREATE TABLE member_tasks (
|
CREATE TABLE member_tasks (
|
||||||
id bigint NOT NULL,
|
id bigint NOT NULL,
|
||||||
member_id bigint NOT NULL,
|
member_id bigint NOT NULL,
|
||||||
|
@ -17034,7 +17052,8 @@ CREATE TABLE members (
|
||||||
override boolean DEFAULT false NOT NULL,
|
override boolean DEFAULT false NOT NULL,
|
||||||
state smallint DEFAULT 0,
|
state smallint DEFAULT 0,
|
||||||
invite_email_success boolean DEFAULT true NOT NULL,
|
invite_email_success boolean DEFAULT true NOT NULL,
|
||||||
member_namespace_id bigint
|
member_namespace_id bigint,
|
||||||
|
member_role_id bigint
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE SEQUENCE members_id_seq
|
CREATE SEQUENCE members_id_seq
|
||||||
|
@ -23285,6 +23304,8 @@ ALTER TABLE ONLY lists ALTER COLUMN id SET DEFAULT nextval('lists_id_seq'::regcl
|
||||||
|
|
||||||
ALTER TABLE ONLY loose_foreign_keys_deleted_records ALTER COLUMN id SET DEFAULT nextval('loose_foreign_keys_deleted_records_id_seq'::regclass);
|
ALTER TABLE ONLY loose_foreign_keys_deleted_records ALTER COLUMN id SET DEFAULT nextval('loose_foreign_keys_deleted_records_id_seq'::regclass);
|
||||||
|
|
||||||
|
ALTER TABLE ONLY member_roles ALTER COLUMN id SET DEFAULT nextval('member_roles_id_seq'::regclass);
|
||||||
|
|
||||||
ALTER TABLE ONLY member_tasks ALTER COLUMN id SET DEFAULT nextval('member_tasks_id_seq'::regclass);
|
ALTER TABLE ONLY member_tasks ALTER COLUMN id SET DEFAULT nextval('member_tasks_id_seq'::regclass);
|
||||||
|
|
||||||
ALTER TABLE ONLY members ALTER COLUMN id SET DEFAULT nextval('members_id_seq'::regclass);
|
ALTER TABLE ONLY members ALTER COLUMN id SET DEFAULT nextval('members_id_seq'::regclass);
|
||||||
|
@ -25260,6 +25281,9 @@ ALTER TABLE ONLY lists
|
||||||
ALTER TABLE ONLY loose_foreign_keys_deleted_records
|
ALTER TABLE ONLY loose_foreign_keys_deleted_records
|
||||||
ADD CONSTRAINT loose_foreign_keys_deleted_records_pkey PRIMARY KEY (partition, id);
|
ADD CONSTRAINT loose_foreign_keys_deleted_records_pkey PRIMARY KEY (partition, id);
|
||||||
|
|
||||||
|
ALTER TABLE ONLY member_roles
|
||||||
|
ADD CONSTRAINT member_roles_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
ALTER TABLE ONLY member_tasks
|
ALTER TABLE ONLY member_tasks
|
||||||
ADD CONSTRAINT member_tasks_pkey PRIMARY KEY (id);
|
ADD CONSTRAINT member_tasks_pkey PRIMARY KEY (id);
|
||||||
|
|
||||||
|
@ -28695,6 +28719,8 @@ CREATE INDEX index_lists_on_user_id ON lists USING btree (user_id);
|
||||||
|
|
||||||
CREATE INDEX index_loose_foreign_keys_deleted_records_for_partitioned_query ON ONLY loose_foreign_keys_deleted_records USING btree (partition, fully_qualified_table_name, consume_after, id) WHERE (status = 1);
|
CREATE INDEX index_loose_foreign_keys_deleted_records_for_partitioned_query ON ONLY loose_foreign_keys_deleted_records USING btree (partition, fully_qualified_table_name, consume_after, id) WHERE (status = 1);
|
||||||
|
|
||||||
|
CREATE INDEX index_member_roles_on_namespace_id ON member_roles USING btree (namespace_id);
|
||||||
|
|
||||||
CREATE INDEX index_member_tasks_on_member_id ON member_tasks USING btree (member_id);
|
CREATE INDEX index_member_tasks_on_member_id ON member_tasks USING btree (member_id);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX index_member_tasks_on_member_id_and_project_id ON member_tasks USING btree (member_id, project_id);
|
CREATE UNIQUE INDEX index_member_tasks_on_member_id_and_project_id ON member_tasks USING btree (member_id, project_id);
|
||||||
|
@ -28711,6 +28737,8 @@ CREATE UNIQUE INDEX index_members_on_invite_token ON members USING btree (invite
|
||||||
|
|
||||||
CREATE INDEX index_members_on_member_namespace_id ON members USING btree (member_namespace_id);
|
CREATE INDEX index_members_on_member_namespace_id ON members USING btree (member_namespace_id);
|
||||||
|
|
||||||
|
CREATE INDEX index_members_on_member_role_id ON members USING btree (member_role_id);
|
||||||
|
|
||||||
CREATE INDEX index_members_on_non_requested_non_invited_and_state_awaiting ON members USING btree (source_id) WHERE ((requested_at IS NULL) AND (invite_token IS NULL) AND (access_level > 5) AND (state = 1));
|
CREATE INDEX index_members_on_non_requested_non_invited_and_state_awaiting ON members USING btree (source_id) WHERE ((requested_at IS NULL) AND (invite_token IS NULL) AND (access_level > 5) AND (state = 1));
|
||||||
|
|
||||||
CREATE INDEX index_members_on_requested_at ON members USING btree (requested_at);
|
CREATE INDEX index_members_on_requested_at ON members USING btree (requested_at);
|
||||||
|
@ -31996,6 +32024,9 @@ ALTER TABLE ONLY dast_scanner_profiles_builds
|
||||||
ALTER TABLE ONLY issue_assignees
|
ALTER TABLE ONLY issue_assignees
|
||||||
ADD CONSTRAINT fk_5e0c8d9154 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_5e0c8d9154 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY members
|
||||||
|
ADD CONSTRAINT fk_5e12d50db3 FOREIGN KEY (member_role_id) REFERENCES member_roles(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
ALTER TABLE ONLY csv_issue_imports
|
ALTER TABLE ONLY csv_issue_imports
|
||||||
ADD CONSTRAINT fk_5e1572387c FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_5e1572387c FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
@ -33898,6 +33929,9 @@ ALTER TABLE ONLY resource_milestone_events
|
||||||
ALTER TABLE ONLY resource_iteration_events
|
ALTER TABLE ONLY resource_iteration_events
|
||||||
ADD CONSTRAINT fk_rails_cee126f66c FOREIGN KEY (iteration_id) REFERENCES sprints(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_rails_cee126f66c FOREIGN KEY (iteration_id) REFERENCES sprints(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE ONLY member_roles
|
||||||
|
ADD CONSTRAINT fk_rails_cf0ee35814 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
ALTER TABLE ONLY upload_states
|
ALTER TABLE ONLY upload_states
|
||||||
ADD CONSTRAINT fk_rails_d00f153613 FOREIGN KEY (upload_id) REFERENCES uploads(id) ON DELETE CASCADE;
|
ADD CONSTRAINT fk_rails_d00f153613 FOREIGN KEY (upload_id) REFERENCES uploads(id) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
|
|
@ -19098,7 +19098,6 @@ Values for sorting runners.
|
||||||
| <a id="cirunnerupgradestatustypeinvalid"></a>`INVALID` | Runner version is not valid. |
|
| <a id="cirunnerupgradestatustypeinvalid"></a>`INVALID` | Runner version is not valid. |
|
||||||
| <a id="cirunnerupgradestatustypenot_available"></a>`NOT_AVAILABLE` | Upgrade is not available for the runner. |
|
| <a id="cirunnerupgradestatustypenot_available"></a>`NOT_AVAILABLE` | Upgrade is not available for the runner. |
|
||||||
| <a id="cirunnerupgradestatustyperecommended"></a>`RECOMMENDED` | Upgrade is available and recommended for the runner. |
|
| <a id="cirunnerupgradestatustyperecommended"></a>`RECOMMENDED` | Upgrade is available and recommended for the runner. |
|
||||||
| <a id="cirunnerupgradestatustypeunknown"></a>`UNKNOWN` | Upgrade status is unknown. |
|
|
||||||
|
|
||||||
### `CiVariableType`
|
### `CiVariableType`
|
||||||
|
|
||||||
|
|
|
@ -509,11 +509,9 @@ your own suggestions to the merge request. Note that:
|
||||||
has more than one commit, then see the note below about rewriting
|
has more than one commit, then see the note below about rewriting
|
||||||
commit history.
|
commit history.
|
||||||
|
|
||||||
As a maintainer, if a merge request that you authored has received all required approvals, it is acceptable to show a [bias for action](https://about.gitlab.com/handbook/values/#bias-for-action) and merge your own MR, if:
|
Authors are not authorized to merge their own merge requests and need to seek another maintainer to merge.
|
||||||
|
This policy is in place to satisfy the CHG-04 control of the GitLab
|
||||||
- The last maintainer to review intended to start the merge and did not, OR
|
[Change Management Controls](https://about.gitlab.com/handbook/engineering/security/security-assurance/security-compliance/guidance/change-management.html).
|
||||||
- The last maintainer to review started the merge, but some trivial chore caused the pipeline to break. For example, the MR might need a rebase first because of unrelated pipeline issues, or some files might need to be regenerated (like `gitlab.pot`).
|
|
||||||
- "Trivial" is a subjective measure but we expect project maintainers to exercise their judgement carefully and cautiously.
|
|
||||||
|
|
||||||
When ready to merge:
|
When ready to merge:
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,6 @@ are very appreciative of the work done by translators and proofreaders!
|
||||||
- German
|
- German
|
||||||
- Michael Hahnle - [GitLab](https://gitlab.com/mhah), [Crowdin](https://crowdin.com/profile/mhah)
|
- Michael Hahnle - [GitLab](https://gitlab.com/mhah), [Crowdin](https://crowdin.com/profile/mhah)
|
||||||
- Katrin Leinweber - [GitLab](https://gitlab.com/katrinleinweber), [Crowdin](https://crowdin.com/profile/katrinleinweber)
|
- Katrin Leinweber - [GitLab](https://gitlab.com/katrinleinweber), [Crowdin](https://crowdin.com/profile/katrinleinweber)
|
||||||
- Justman10000 - [GitLab](https://gitlab.com/Justman10000), [Crowdin](https://crowdin.com/profile/Justman10000)
|
|
||||||
- Vladislav Wanner - [GitLab](https://gitlab.com/RumBugen), [Crowdin](https://crowdin.com/profile/RumBugen)
|
- Vladislav Wanner - [GitLab](https://gitlab.com/RumBugen), [Crowdin](https://crowdin.com/profile/RumBugen)
|
||||||
- Greek
|
- Greek
|
||||||
- Proofreaders needed.
|
- Proofreaders needed.
|
||||||
|
|
|
@ -497,11 +497,12 @@ We recommend that you use the identifiers the GitLab scanners already define:
|
||||||
|------------|------|---------------|
|
|------------|------|---------------|
|
||||||
| [CVE](https://cve.mitre.org/cve/) | `cve` | CVE-2019-10086 |
|
| [CVE](https://cve.mitre.org/cve/) | `cve` | CVE-2019-10086 |
|
||||||
| [CWE](https://cwe.mitre.org/data/index.html) | `cwe` | CWE-1026 |
|
| [CWE](https://cwe.mitre.org/data/index.html) | `cwe` | CWE-1026 |
|
||||||
|
| [ELSA](https://linux.oracle.com/security/) | `elsa` | ELSA-2020-0085 |
|
||||||
| [OSVD](https://cve.mitre.org/data/refs/refmap/source-OSVDB.html) | `osvdb` | OSVDB-113928 |
|
| [OSVD](https://cve.mitre.org/data/refs/refmap/source-OSVDB.html) | `osvdb` | OSVDB-113928 |
|
||||||
|
| [OWASP](https://owasp.org/Top10/) | `owasp` | A01:2021–Broken Access Control Design |
|
||||||
|
| [RHSA](https://access.redhat.com/errata/#/) | `rhsa` | RHSA-2020:0111 |
|
||||||
| [USN](https://ubuntu.com/security/notices) | `usn` | USN-4234-1 |
|
| [USN](https://ubuntu.com/security/notices) | `usn` | USN-4234-1 |
|
||||||
| [WASC](http://projects.webappsec.org/Threat-Classification-Reference-Grid) | `wasc` | WASC-19 |
|
| [WASC](http://projects.webappsec.org/Threat-Classification-Reference-Grid) | `wasc` | WASC-19 |
|
||||||
| [RHSA](https://access.redhat.com/errata/#/) | `rhsa` | RHSA-2020:0111 |
|
|
||||||
| [ELSA](https://linux.oracle.com/security/) | `elsa` | ELSA-2020-0085 |
|
|
||||||
|
|
||||||
The generic identifiers listed above are defined in the [common library](https://gitlab.com/gitlab-org/security-products/analyzers/common),
|
The generic identifiers listed above are defined in the [common library](https://gitlab.com/gitlab-org/security-products/analyzers/common),
|
||||||
which is shared by some of the analyzers that GitLab maintains. You can [contribute](https://gitlab.com/gitlab-org/security-products/analyzers/common/blob/master/issue/identifier.go)
|
which is shared by some of the analyzers that GitLab maintains. You can [contribute](https://gitlab.com/gitlab-org/security-products/analyzers/common/blob/master/issue/identifier.go)
|
||||||
|
|
|
@ -22,9 +22,7 @@ and sales teams understand how GitLab is used. The data helps to:
|
||||||
Service Ping information is not anonymous. It's linked to the instance's hostname, but does
|
Service Ping information is not anonymous. It's linked to the instance's hostname, but does
|
||||||
not contain project names, usernames, or any other specific data.
|
not contain project names, usernames, or any other specific data.
|
||||||
|
|
||||||
Sending a Service Ping payload is optional and you can [disable](../../user/admin_area/settings/usage_statistics.md#enable-or-disable-usage-statistics) it on any
|
Service Ping is enabled by default. However, you can [disable](../../user/admin_area/settings/usage_statistics.md#enable-or-disable-usage-statistics) it on any self-managed instance. When Service Ping is enabled, GitLab gathers data from the other instances and can show your instance's usage statistics to your users.
|
||||||
self-managed instance. When Service Ping is enabled, GitLab gathers data from the other instances
|
|
||||||
and can show your instance's usage statistics to your users.
|
|
||||||
|
|
||||||
## Service Ping terminology
|
## Service Ping terminology
|
||||||
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
|
@ -376,6 +376,15 @@ for synchronous communication during incident management. After starting a Zoom
|
||||||
call for an incident, you can associate the conference call with an issue. Your
|
call for an incident, you can associate the conference call with an issue. Your
|
||||||
team members can join the Zoom call without requesting a link.
|
team members can join the Zoom call without requesting a link.
|
||||||
|
|
||||||
|
### Linked resources
|
||||||
|
|
||||||
|
In an incident, you can [links to various resources](linked_resources.md),
|
||||||
|
for example:
|
||||||
|
|
||||||
|
- The incident Slack channel
|
||||||
|
- Zoom meeting
|
||||||
|
- Resources for resolving the incidents
|
||||||
|
|
||||||
### Embed metrics in incidents
|
### Embed metrics in incidents
|
||||||
|
|
||||||
You can embed metrics anywhere [GitLab Markdown](../../user/markdown.md) is
|
You can embed metrics anywhere [GitLab Markdown](../../user/markdown.md) is
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
---
|
||||||
|
stage: Monitor
|
||||||
|
group: Respond
|
||||||
|
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
|
||||||
|
---
|
||||||
|
|
||||||
|
# Linked resources in incidents **(PREMIUM)**
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/230852) in GitLab 15.3 [with a flag](../../administration/feature_flags.md) named `incident_resource_links_widget`. Enabled on GitLab.com. Disabled on self-managed.
|
||||||
|
|
||||||
|
FLAG:
|
||||||
|
On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `incident_resource_links_widget`.
|
||||||
|
On GitLab.com, this feature is available.
|
||||||
|
|
||||||
|
To help your team members find the important links without having to search through many comments,
|
||||||
|
you can add linked resources to an incident issue.
|
||||||
|
|
||||||
|
Resources you might want link to:
|
||||||
|
|
||||||
|
- Zoom meetings
|
||||||
|
- Slack channels or threads
|
||||||
|
- Google Docs
|
||||||
|
|
||||||
|
## View linked resources of an incident
|
||||||
|
|
||||||
|
Linked resources for an incident are listed under the **Summary** tab.
|
||||||
|
|
||||||
|
![Linked resources list](img/linked_resources_list_v15_3.png)
|
||||||
|
|
||||||
|
To view the linked resources of an incident:
|
||||||
|
|
||||||
|
1. On the top bar, select **Menu > Projects** and find your project.
|
||||||
|
1. On the left sidebar, select **Monitor > Incidents**.
|
||||||
|
1. Select an incident.
|
||||||
|
|
||||||
|
## Add a linked resource
|
||||||
|
|
||||||
|
Add a linked resource manually from an incident.
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- You must have at least the Reporter role for the project.
|
||||||
|
|
||||||
|
To add a linked resource:
|
||||||
|
|
||||||
|
1. On the top bar, select **Menu > Projects** and find your project.
|
||||||
|
1. On the left sidebar, select **Monitor > Incidents**.
|
||||||
|
1. Select an incident.
|
||||||
|
1. In the **Linked resources** section, select the plus icon (**{plus-square}**).
|
||||||
|
1. Complete the required fields.
|
||||||
|
1. Select **Add**.
|
||||||
|
|
||||||
|
## Remove a linked resource
|
||||||
|
|
||||||
|
You can also remove a linked resource.
|
||||||
|
|
||||||
|
Prerequisities:
|
||||||
|
|
||||||
|
- You must have at least the Reporter role for the project.
|
||||||
|
|
||||||
|
To remove a linked resource:
|
||||||
|
|
||||||
|
1. On the top bar, select **Menu > Projects** and find your project.
|
||||||
|
1. On the left sidebar, select **Monitor > Incidents**.
|
||||||
|
1. Select an incident.
|
||||||
|
1. In the **Linked resources** section, select **Remove** (**{close}**).
|
|
@ -300,6 +300,7 @@ licenses: :gitlab_main
|
||||||
lists: :gitlab_main
|
lists: :gitlab_main
|
||||||
list_user_preferences: :gitlab_main
|
list_user_preferences: :gitlab_main
|
||||||
loose_foreign_keys_deleted_records: :gitlab_shared
|
loose_foreign_keys_deleted_records: :gitlab_shared
|
||||||
|
member_roles: :gitlab_main
|
||||||
member_tasks: :gitlab_main
|
member_tasks: :gitlab_main
|
||||||
members: :gitlab_main
|
members: :gitlab_main
|
||||||
merge_request_assignees: :gitlab_main
|
merge_request_assignees: :gitlab_main
|
||||||
|
|
|
@ -748,6 +748,9 @@ msgstr ""
|
||||||
msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
|
msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "%{labelStart}Project:%{labelEnd} %{project}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
|
msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -182,7 +182,7 @@ RSpec.describe 'Issue Sidebar' do
|
||||||
|
|
||||||
page.within '.dropdown-menu-user' do
|
page.within '.dropdown-menu-user' do
|
||||||
expect(page).not_to have_content 'Unassigned'
|
expect(page).not_to have_content 'Unassigned'
|
||||||
click_link user2.name
|
click_button user2.name
|
||||||
end
|
end
|
||||||
|
|
||||||
find('.participants').click
|
find('.participants').click
|
||||||
|
|
|
@ -285,7 +285,7 @@ RSpec.describe "Issues > User edits issue", :js do
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within '.dropdown-menu-user' do
|
page.within '.dropdown-menu-user' do
|
||||||
click_link user.name
|
click_button user.name
|
||||||
end
|
end
|
||||||
|
|
||||||
page.within('.assignee') do
|
page.within('.assignee') do
|
||||||
|
@ -306,7 +306,7 @@ RSpec.describe "Issues > User edits issue", :js do
|
||||||
|
|
||||||
click_button('Edit')
|
click_button('Edit')
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
click_link user.name
|
click_button user.name
|
||||||
|
|
||||||
find('[data-testid="title"]').click
|
find('[data-testid="title"]').click
|
||||||
wait_for_requests
|
wait_for_requests
|
||||||
|
|
|
@ -92,10 +92,8 @@ RSpec.describe Ci::RunnersFinder do
|
||||||
context 'set to an invalid value' do
|
context 'set to an invalid value' do
|
||||||
let(:upgrade_status) { :some_invalid_status }
|
let(:upgrade_status) { :some_invalid_status }
|
||||||
|
|
||||||
it 'does not call with_upgrade_status' do
|
it 'raises ArgumentError' do
|
||||||
expect(Ci::Runner).not_to receive(:with_upgrade_status)
|
expect { execute }.to raise_error(ArgumentError)
|
||||||
|
|
||||||
expect(execute).to match_array(Ci::Runner.all)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -11,22 +11,17 @@ exports[`~/access_tokens/components/expires_at_field should render datepicker wi
|
||||||
arialabel=""
|
arialabel=""
|
||||||
autocomplete=""
|
autocomplete=""
|
||||||
container=""
|
container=""
|
||||||
|
data-qa-selector="expiry_date_field"
|
||||||
|
defaultdate="Wed Aug 05 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
|
||||||
displayfield="true"
|
displayfield="true"
|
||||||
firstday="0"
|
firstday="0"
|
||||||
|
inputid="personal_access_token_expires_at"
|
||||||
inputlabel="Enter date"
|
inputlabel="Enter date"
|
||||||
|
inputname="personal_access_token[expires_at]"
|
||||||
mindate="Mon Jul 06 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
|
mindate="Mon Jul 06 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
|
||||||
placeholder="YYYY-MM-DD"
|
placeholder="YYYY-MM-DD"
|
||||||
|
showclearbutton="true"
|
||||||
theme=""
|
theme=""
|
||||||
>
|
/>
|
||||||
<gl-form-input-stub
|
|
||||||
autocomplete="off"
|
|
||||||
class="datepicker gl-datepicker-input"
|
|
||||||
data-qa-selector="expiry_date_field"
|
|
||||||
id="personal_access_token_expires_at"
|
|
||||||
inputmode="none"
|
|
||||||
name="personal_access_token[expires_at]"
|
|
||||||
placeholder="YYYY-MM-DD"
|
|
||||||
/>
|
|
||||||
</gl-datepicker-stub>
|
|
||||||
</gl-form-group-stub>
|
</gl-form-group-stub>
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { GlDatepicker } from '@gitlab/ui';
|
import { GlDatepicker } from '@gitlab/ui';
|
||||||
import ExpiresAtField from '~/access_tokens/components/expires_at_field.vue';
|
import ExpiresAtField from '~/access_tokens/components/expires_at_field.vue';
|
||||||
|
import { getDateInFuture } from '~/lib/utils/datetime_utility';
|
||||||
|
|
||||||
describe('~/access_tokens/components/expires_at_field', () => {
|
describe('~/access_tokens/components/expires_at_field', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
|
@ -49,4 +50,12 @@ describe('~/access_tokens/components/expires_at_field', () => {
|
||||||
|
|
||||||
expect(findDatepicker().props('maxDate')).toStrictEqual(maxDate);
|
expect(findDatepicker().props('maxDate')).toStrictEqual(maxDate);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the default expiration date to be 30 days', () => {
|
||||||
|
const today = new Date();
|
||||||
|
const future = getDateInFuture(today, 30);
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
expect(findDatepicker().props('defaultDate')).toStrictEqual(future);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1195,7 +1195,7 @@ Oranges are orange [^1]
|
||||||
${'link'} | ${'link <https://www.gitlab.com>'} | ${'modified link <https://www.gitlab.com>'} | ${prependContentEditAction}
|
${'link'} | ${'link <https://www.gitlab.com>'} | ${'modified link <https://www.gitlab.com>'} | ${prependContentEditAction}
|
||||||
${'link'} | ${'link [https://www.gitlab.com>'} | ${'modified link \\[https://www.gitlab.com>'} | ${prependContentEditAction}
|
${'link'} | ${'link [https://www.gitlab.com>'} | ${'modified link \\[https://www.gitlab.com>'} | ${prependContentEditAction}
|
||||||
${'link'} | ${'link <https://www.gitlab.com'} | ${'modified link <https://www.gitlab.com'} | ${prependContentEditAction}
|
${'link'} | ${'link <https://www.gitlab.com'} | ${'modified link <https://www.gitlab.com'} | ${prependContentEditAction}
|
||||||
${'link'} | ${'link https://www.gitlab.com>'} | ${'modified link https://www.gitlab.com>'} | ${prependContentEditAction}
|
${'link'} | ${'link https://www.gitlab.com>'} | ${'modified link [https://www.gitlab.com>](https://www.gitlab.com%3E)'} | ${prependContentEditAction}
|
||||||
${'link'} | ${'link **https://www.gitlab.com]**'} | ${'modified link [**https://www.gitlab.com\\]**](https://www.gitlab.com%5D)'} | ${prependContentEditAction}
|
${'link'} | ${'link **https://www.gitlab.com]**'} | ${'modified link [**https://www.gitlab.com\\]**](https://www.gitlab.com%5D)'} | ${prependContentEditAction}
|
||||||
${'code'} | ${'`code`'} | ${'`code modified`'} | ${defaultEditAction}
|
${'code'} | ${'`code`'} | ${'`code modified`'} | ${defaultEditAction}
|
||||||
${'code'} | ${'<code>code</code>'} | ${'<code>code modified</code>'} | ${defaultEditAction}
|
${'code'} | ${'<code>code</code>'} | ${'<code>code modified</code>'} | ${defaultEditAction}
|
||||||
|
|
|
@ -555,18 +555,22 @@ describe('URL utility', () => {
|
||||||
|
|
||||||
describe('relativePathToAbsolute', () => {
|
describe('relativePathToAbsolute', () => {
|
||||||
it.each`
|
it.each`
|
||||||
path | base | result
|
path | base | result
|
||||||
${'./foo'} | ${'bar/'} | ${'/bar/foo'}
|
${'./foo'} | ${'bar/'} | ${'/bar/foo'}
|
||||||
${'../john.md'} | ${'bar/baz/foo.php'} | ${'/bar/john.md'}
|
${'../john.md'} | ${'bar/baz/foo.php'} | ${'/bar/john.md'}
|
||||||
${'../images/img.png'} | ${'bar/baz/foo.php'} | ${'/bar/images/img.png'}
|
${'../images/img.png'} | ${'bar/baz/foo.php'} | ${'/bar/images/img.png'}
|
||||||
${'../images/Image 1.png'} | ${'bar/baz/foo.php'} | ${'/bar/images/Image 1.png'}
|
${'../images/Image 1.png'} | ${'bar/baz/foo.php'} | ${'/bar/images/Image 1.png'}
|
||||||
${'/images/img.png'} | ${'bar/baz/foo.php'} | ${'/images/img.png'}
|
${'/images/img.png'} | ${'bar/baz/foo.php'} | ${'/images/img.png'}
|
||||||
${'/images/img.png'} | ${'/bar/baz/foo.php'} | ${'/images/img.png'}
|
${'/images/img.png'} | ${'bar/baz//foo.php'} | ${'/images/img.png'}
|
||||||
${'../john.md'} | ${'/bar/baz/foo.php'} | ${'/bar/john.md'}
|
${'/images//img.png'} | ${'bar/baz/foo.php'} | ${'/images/img.png'}
|
||||||
${'../john.md'} | ${'///bar/baz/foo.php'} | ${'/bar/john.md'}
|
${'/images/img.png'} | ${'/bar/baz/foo.php'} | ${'/images/img.png'}
|
||||||
${'/images/img.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/images/img.png'}
|
${'../john.md'} | ${'/bar/baz/foo.php'} | ${'/bar/john.md'}
|
||||||
${'../images/img.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/user/images/img.png'}
|
${'../john.md'} | ${'///bar/baz/foo.php'} | ${'/bar/john.md'}
|
||||||
${'../images/Image 1.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/user/images/Image%201.png'}
|
${'/images/img.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/images/img.png'}
|
||||||
|
${'/images/img.png'} | ${'https://gitlab.com////user/project/'} | ${'https://gitlab.com/images/img.png'}
|
||||||
|
${'/images////img.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/images/img.png'}
|
||||||
|
${'../images/img.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/user/images/img.png'}
|
||||||
|
${'../images/Image 1.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/user/images/Image%201.png'}
|
||||||
`(
|
`(
|
||||||
'converts relative path "$path" with base "$base" to absolute path => "expected"',
|
'converts relative path "$path" with base "$base" to absolute path => "expected"',
|
||||||
({ path, base, result }) => {
|
({ path, base, result }) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { GlEmptyState, GlBadge, GlTabs, GlTab } from '@gitlab/ui';
|
import { GlEmptyState, GlBadge, GlTabs, GlTab } from '@gitlab/ui';
|
||||||
import Vue, { nextTick } from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import VueApollo from 'vue-apollo';
|
import VueApollo from 'vue-apollo';
|
||||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||||
|
@ -303,6 +303,8 @@ describe('PackagesApp', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleting a file', () => {
|
describe('deleting a file', () => {
|
||||||
|
let showDeleteFileSpy;
|
||||||
|
let showDeletePackageSpy;
|
||||||
const [fileToDelete] = packageFiles();
|
const [fileToDelete] = packageFiles();
|
||||||
|
|
||||||
const doDeleteFile = () => {
|
const doDeleteFile = () => {
|
||||||
|
@ -313,16 +315,48 @@ describe('PackagesApp', () => {
|
||||||
return waitForPromises();
|
return waitForPromises();
|
||||||
};
|
};
|
||||||
|
|
||||||
it('opens a confirmation modal', async () => {
|
it('opens delete file confirmation modal', async () => {
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
await waitForPromises();
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(findDeleteFileModal().exists()).toBe(true);
|
||||||
|
|
||||||
|
showDeleteFileSpy = jest.spyOn(wrapper.vm.$refs.deleteFileModal, 'show');
|
||||||
|
showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show');
|
||||||
|
|
||||||
findPackageFiles().vm.$emit('delete-file', fileToDelete);
|
findPackageFiles().vm.$emit('delete-file', fileToDelete);
|
||||||
|
|
||||||
await nextTick();
|
expect(showDeletePackageSpy).not.toBeCalled();
|
||||||
|
expect(showDeleteFileSpy).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
expect(findDeleteFileModal().exists()).toBe(true);
|
it('when its the only file opens delete package confirmation modal', async () => {
|
||||||
|
const [packageFile] = packageFiles();
|
||||||
|
const resolver = jest.fn().mockResolvedValue(
|
||||||
|
packageDetailsQuery({
|
||||||
|
packageFiles: {
|
||||||
|
nodes: [packageFile],
|
||||||
|
__typename: 'PackageFileConnection',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
createComponent({
|
||||||
|
resolver,
|
||||||
|
});
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(findDeleteModal().exists()).toBe(true);
|
||||||
|
|
||||||
|
showDeleteFileSpy = jest.spyOn(wrapper.vm.$refs.deleteFileModal, 'show');
|
||||||
|
showDeletePackageSpy = jest.spyOn(wrapper.vm.$refs.deleteModal, 'show');
|
||||||
|
|
||||||
|
findPackageFiles().vm.$emit('delete-file', fileToDelete);
|
||||||
|
|
||||||
|
expect(showDeletePackageSpy).toBeCalled();
|
||||||
|
expect(showDeleteFileSpy).not.toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('confirming on the modal deletes the file and shows a success message', async () => {
|
it('confirming on the modal deletes the file and shows a success message', async () => {
|
||||||
|
|
|
@ -343,6 +343,14 @@ export const issuableQueryResponse = {
|
||||||
__typename: 'Issue',
|
__typename: 'Issue',
|
||||||
id: 'gid://gitlab/Issue/1',
|
id: 'gid://gitlab/Issue/1',
|
||||||
iid: '1',
|
iid: '1',
|
||||||
|
author: {
|
||||||
|
id: '1',
|
||||||
|
avatarUrl: '/avatar',
|
||||||
|
name: 'root',
|
||||||
|
username: 'root',
|
||||||
|
webUrl: 'root',
|
||||||
|
status: null,
|
||||||
|
},
|
||||||
assignees: {
|
assignees: {
|
||||||
nodes: [
|
nodes: [
|
||||||
{
|
{
|
||||||
|
@ -450,7 +458,7 @@ export const subscriptionResponse = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockUser1 = {
|
export const mockUser1 = {
|
||||||
__typename: 'UserCore',
|
__typename: 'UserCore',
|
||||||
id: 'gid://gitlab/User/1',
|
id: 'gid://gitlab/User/1',
|
||||||
avatarUrl:
|
avatarUrl:
|
||||||
|
@ -459,6 +467,7 @@ const mockUser1 = {
|
||||||
username: 'root',
|
username: 'root',
|
||||||
webUrl: '/root',
|
webUrl: '/root',
|
||||||
status: null,
|
status: null,
|
||||||
|
canMerge: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mockUser2 = {
|
export const mockUser2 = {
|
||||||
|
@ -469,6 +478,7 @@ export const mockUser2 = {
|
||||||
username: 'rookie',
|
username: 'rookie',
|
||||||
webUrl: 'rookie',
|
webUrl: 'rookie',
|
||||||
status: null,
|
status: null,
|
||||||
|
canMerge: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const searchResponse = {
|
export const searchResponse = {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue