Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2977cf67ec
commit
3b060a68f3
|
@ -1,7 +1,8 @@
|
|||
<script>
|
||||
import { GlDatepicker, GlFormInput, GlFormGroup } from '@gitlab/ui';
|
||||
import { GlDatepicker, GlFormGroup } from '@gitlab/ui';
|
||||
|
||||
import { __ } from '~/locale';
|
||||
import { getDateInFuture } from '~/lib/utils/datetime_utility';
|
||||
|
||||
export default {
|
||||
name: 'ExpiresAtField',
|
||||
|
@ -10,7 +11,6 @@ export default {
|
|||
},
|
||||
components: {
|
||||
GlDatepicker,
|
||||
GlFormInput,
|
||||
GlFormGroup,
|
||||
MaxExpirationDateMessage: () =>
|
||||
import('ee_component/access_tokens/components/max_expiration_date_message.vue'),
|
||||
|
@ -32,20 +32,28 @@ export default {
|
|||
default: () => null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
in30Days() {
|
||||
const today = new Date();
|
||||
return getDateInFuture(today, 30);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-form-group :label="$options.i18n.label" :label-for="inputAttrs.id">
|
||||
<gl-datepicker :target="null" :min-date="minDate" :max-date="maxDate">
|
||||
<gl-form-input
|
||||
v-bind="inputAttrs"
|
||||
class="datepicker gl-datepicker-input"
|
||||
autocomplete="off"
|
||||
inputmode="none"
|
||||
<gl-datepicker
|
||||
:target="null"
|
||||
:min-date="minDate"
|
||||
:max-date="maxDate"
|
||||
:default-date="in30Days"
|
||||
show-clear-button
|
||||
:input-name="inputAttrs.name"
|
||||
:input-id="inputAttrs.id"
|
||||
:placeholder="inputAttrs.placeholder"
|
||||
data-qa-selector="expiry_date_field"
|
||||
/>
|
||||
</gl-datepicker>
|
||||
<template #description>
|
||||
<max-expiration-date-message :max-date="maxDate" />
|
||||
</template>
|
||||
|
|
|
@ -397,6 +397,7 @@ export function relativePathToAbsolute(path, basePath) {
|
|||
const absolute = isAbsolute(basePath);
|
||||
const base = absolute ? basePath : `file:///${basePath}`;
|
||||
const url = new URL(path, base);
|
||||
url.pathname = url.pathname.replace(/\/\/+/g, '/');
|
||||
return absolute ? url.href : decodeURIComponent(url.pathname);
|
||||
}
|
||||
|
||||
|
|
|
@ -208,8 +208,12 @@ export default {
|
|||
},
|
||||
handleFileDelete(file) {
|
||||
this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
|
||||
if (this.packageFiles.length === 1) {
|
||||
this.$refs.deleteModal.show();
|
||||
} else {
|
||||
this.fileToDelete = { ...file };
|
||||
this.$refs.deleteFileModal.show();
|
||||
}
|
||||
},
|
||||
confirmFileDelete() {
|
||||
this.track(DELETE_PACKAGE_FILE_TRACKING_ACTION);
|
||||
|
|
|
@ -38,11 +38,10 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gl-display-flex gl-pb-4">
|
||||
<dt class="gl-mr-2">{{ label }}</dt>
|
||||
<dd class="gl-mb-0">
|
||||
<!-- eslint-disable-next-line @gitlab/vue-prefer-dollar-scopedslots -->
|
||||
<template v-if="value || $slots.value">
|
||||
<div class="gl-display-contents">
|
||||
<dt class="gl-mb-5 gl-mr-6 gl-max-w-26">{{ label }}</dt>
|
||||
<dd class="gl-mb-5">
|
||||
<template v-if="value || $scopedSlots.value">
|
||||
<slot name="value">{{ value }}</slot>
|
||||
</template>
|
||||
<span v-else class="gl-text-gray-500">{{ emptyValue }}</span>
|
||||
|
|
|
@ -51,6 +51,9 @@ export default {
|
|||
}
|
||||
return null;
|
||||
},
|
||||
tagList() {
|
||||
return this.runner.tagList || [];
|
||||
},
|
||||
isGroupRunner() {
|
||||
return this.runner?.runnerType === GROUP_TYPE;
|
||||
},
|
||||
|
@ -66,14 +69,17 @@ export default {
|
|||
<div>
|
||||
<runner-upgrade-status-alert class="gl-my-4" :runner="runner" />
|
||||
<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|Last contact')"
|
||||
:empty-value="s__('Runners|Never contacted')"
|
||||
>
|
||||
<template #value>
|
||||
<time-ago v-if="runner.contactedAt" :time="runner.contactedAt" />
|
||||
<template v-if="runner.contactedAt" #value>
|
||||
<time-ago :time="runner.contactedAt" />
|
||||
</template>
|
||||
</runner-detail>
|
||||
<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|Platform')" :value="runner.platformName" />
|
||||
<runner-detail :label="s__('Runners|Configuration')">
|
||||
<template #value>
|
||||
<gl-intersperse v-if="configTextProtected || configTextUntagged">
|
||||
<template v-if="configTextProtected || configTextUntagged" #value>
|
||||
<gl-intersperse>
|
||||
<span v-if="configTextProtected">{{ configTextProtected }}</span>
|
||||
<span v-if="configTextUntagged">{{ configTextUntagged }}</span>
|
||||
</gl-intersperse>
|
||||
|
@ -96,13 +102,8 @@ export default {
|
|||
</runner-detail>
|
||||
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
|
||||
<runner-detail :label="s__('Runners|Tags')">
|
||||
<template #value>
|
||||
<runner-tags
|
||||
v-if="runner.tagList && runner.tagList.length"
|
||||
class="gl-vertical-align-middle"
|
||||
:tag-list="runner.tagList"
|
||||
size="sm"
|
||||
/>
|
||||
<template v-if="tagList.length" #value>
|
||||
<runner-tags class="gl-vertical-align-middle" :tag-list="tagList" size="sm" />
|
||||
</template>
|
||||
</runner-detail>
|
||||
|
||||
|
|
|
@ -149,6 +149,9 @@ export default {
|
|||
signedIn() {
|
||||
return this.currentUser.username !== undefined;
|
||||
},
|
||||
issuableAuthor() {
|
||||
return this.issuable?.author;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
iid(_, oldIid) {
|
||||
|
@ -266,6 +269,7 @@ export default {
|
|||
:current-user="currentUser"
|
||||
:issuable-type="issuableType"
|
||||
:is-editing="edit"
|
||||
:issuable-author="issuableAuthor"
|
||||
class="gl-w-full dropdown-menu-user gl-mt-n3"
|
||||
@toggle="collapseWidget"
|
||||
@error="showError"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { GlAvatarLabeled, GlAvatarLink, GlIcon } from '@gitlab/ui';
|
||||
import { GlAvatarLabeled, GlIcon } from '@gitlab/ui';
|
||||
import { IssuableType } from '~/issues/constants';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
|
||||
|
@ -11,7 +11,6 @@ const AVAILABILITY_STATUS = {
|
|||
export default {
|
||||
components: {
|
||||
GlAvatarLabeled,
|
||||
GlAvatarLink,
|
||||
GlIcon,
|
||||
},
|
||||
props: {
|
||||
|
@ -47,23 +46,21 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<gl-avatar-link>
|
||||
<gl-avatar-labeled
|
||||
:size="32"
|
||||
:label="userLabel"
|
||||
:sub-label="`@${user.username}`"
|
||||
:src="user.avatarUrl || user.avatar || user.avatar_url"
|
||||
class="gl-align-items-center gl-relative"
|
||||
class="gl-align-items-center gl-relative sidebar-participant"
|
||||
>
|
||||
<template #meta>
|
||||
<gl-icon
|
||||
v-if="hasCannotMergeIcon"
|
||||
name="warning-solid"
|
||||
aria-hidden="true"
|
||||
class="merge-icon"
|
||||
class="merge-icon gl-left-6 gl-bottom-0"
|
||||
:size="12"
|
||||
/>
|
||||
</template>
|
||||
</gl-avatar-labeled>
|
||||
</gl-avatar-link>
|
||||
</template>
|
||||
|
|
|
@ -8,6 +8,10 @@ query issueAssignees($fullPath: ID!, $iid: String!) {
|
|||
issuable: issue(iid: $iid) {
|
||||
__typename
|
||||
id
|
||||
author {
|
||||
...User
|
||||
...UserAvailability
|
||||
}
|
||||
assignees {
|
||||
nodes {
|
||||
...User
|
||||
|
|
|
@ -6,6 +6,13 @@ query getMrAssignees($fullPath: ID!, $iid: String!) {
|
|||
id
|
||||
issuable: mergeRequest(iid: $iid) {
|
||||
id
|
||||
author {
|
||||
...User
|
||||
...UserAvailability
|
||||
mergeRequestInteraction {
|
||||
canMerge
|
||||
}
|
||||
}
|
||||
assignees {
|
||||
nodes {
|
||||
...User
|
||||
|
|
|
@ -77,6 +77,11 @@ export default {
|
|||
required: false,
|
||||
default: null,
|
||||
},
|
||||
issuableAuthor: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -178,7 +183,7 @@ export default {
|
|||
[],
|
||||
);
|
||||
|
||||
return this.moveCurrentUserToStart(mergedSearchResults);
|
||||
return this.moveCurrentUserAndAuthorToStart(mergedSearchResults);
|
||||
},
|
||||
isSearchEmpty() {
|
||||
return this.search === '';
|
||||
|
@ -196,14 +201,21 @@ export default {
|
|||
showCurrentUser() {
|
||||
return this.currentUser.username && !this.isCurrentUserInList && this.isSearchEmpty;
|
||||
},
|
||||
showAuthor() {
|
||||
return (
|
||||
this.issuableAuthor &&
|
||||
!this.users.some((user) => user.id === this.issuableAuthor.id) &&
|
||||
this.isSearchEmpty
|
||||
);
|
||||
},
|
||||
selectedFiltered() {
|
||||
if (this.shouldShowParticipants) {
|
||||
return this.moveCurrentUserToStart(this.value);
|
||||
return this.moveCurrentUserAndAuthorToStart(this.value);
|
||||
}
|
||||
|
||||
const foundUsernames = this.users.map(({ username }) => username);
|
||||
const filtered = this.value.filter(({ username }) => foundUsernames.includes(username));
|
||||
return this.moveCurrentUserToStart(filtered);
|
||||
return this.moveCurrentUserAndAuthorToStart(filtered);
|
||||
},
|
||||
selectedUserNames() {
|
||||
return this.value.map(({ username }) => username);
|
||||
|
@ -254,20 +266,22 @@ export default {
|
|||
showDivider(list) {
|
||||
return list.length > 0 && this.isSearchEmpty;
|
||||
},
|
||||
moveCurrentUserToStart(users) {
|
||||
if (!users) {
|
||||
return [];
|
||||
moveCurrentUserAndAuthorToStart(users = []) {
|
||||
let sortedUsers = [...users];
|
||||
|
||||
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) {
|
||||
currentUser.canMerge = this.currentUser.canMerge;
|
||||
const index = usersCopy.indexOf(currentUser);
|
||||
usersCopy.splice(0, 0, usersCopy.splice(index, 1)[0]);
|
||||
sortedUsers = [currentUser, ...sortedUsers.filter((user) => user.id !== currentUser.id)];
|
||||
}
|
||||
|
||||
return usersCopy;
|
||||
return sortedUsers;
|
||||
},
|
||||
setSearchKey(value) {
|
||||
this.search = value.trim();
|
||||
|
@ -298,7 +312,7 @@ export default {
|
|||
<gl-loading-icon
|
||||
v-if="isLoading"
|
||||
data-testid="loading-participants"
|
||||
size="lg"
|
||||
size="md"
|
||||
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
|
||||
/>
|
||||
<template v-else>
|
||||
|
@ -312,8 +326,8 @@ export default {
|
|||
>
|
||||
<span :class="selectedIsEmpty ? 'gl-pl-0' : 'gl-pl-6'" class="gl-font-weight-bold">{{
|
||||
$options.i18n.unassigned
|
||||
}}</span></gl-dropdown-item
|
||||
>
|
||||
}}</span>
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
<gl-dropdown-divider v-if="showDivider(selectedFiltered)" />
|
||||
<gl-dropdown-item
|
||||
|
@ -342,7 +356,17 @@ export default {
|
|||
/>
|
||||
</gl-dropdown-item>
|
||||
</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
|
||||
v-for="unselectedUser in unselectedFiltered"
|
||||
:key="unselectedUser.id"
|
||||
|
|
|
@ -477,6 +477,12 @@
|
|||
height: 2 * $gl-padding;
|
||||
margin: 0 10px 0 0;
|
||||
}
|
||||
|
||||
.sidebar-participant {
|
||||
.merge-icon {
|
||||
top: calc(50% + 5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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
|
||||
|
||||
def filter_by_upgrade_status!
|
||||
return unless @params.key?(:upgrade_status)
|
||||
return unless Ci::RunnerVersion.statuses.key?(@params[:upgrade_status])
|
||||
upgrade_status = @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
|
||||
|
||||
def filter_by_runner_type!
|
||||
|
|
|
@ -8,7 +8,6 @@ module Ci
|
|||
enum_with_nil status: {
|
||||
not_processed: nil,
|
||||
invalid_version: -1,
|
||||
unknown: 0,
|
||||
not_available: 1,
|
||||
available: 2,
|
||||
recommended: 3
|
||||
|
@ -16,7 +15,6 @@ module Ci
|
|||
|
||||
STATUS_DESCRIPTIONS = {
|
||||
invalid_version: 'Runner version is not valid.',
|
||||
unknown: 'Upgrade status is unknown.',
|
||||
not_available: 'Upgrade is not available for the runner.',
|
||||
available: 'Upgrade is available 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
|
||||
# :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 }
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ module Integrations
|
|||
validates :external_wiki_url, presence: true, public_url: true, if: :activated?
|
||||
|
||||
field :external_wiki_url,
|
||||
section: SECTION_TYPE_CONNECTION,
|
||||
title: -> { s_('ExternalWikiService|External wiki URL') },
|
||||
placeholder: -> { s_('ExternalWikiService|https://example.com/xxx/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 }
|
||||
end
|
||||
|
||||
def sections
|
||||
[
|
||||
{
|
||||
type: SECTION_TYPE_CONNECTION,
|
||||
title: s_('Integrations|Connection details'),
|
||||
description: help
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
def execute(_data)
|
||||
response = Gitlab::HTTP.get(properties['external_wiki_url'], verify: true)
|
||||
response.body if response.code == 200
|
||||
|
|
|
@ -28,6 +28,7 @@ class Member < ApplicationRecord
|
|||
belongs_to :user
|
||||
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_role
|
||||
has_one :member_task
|
||||
|
||||
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_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 :member_roles
|
||||
|
||||
has_many :runner_namespaces, inverse_of: :namespace, class_name: 'Ci::RunnerNamespace'
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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")
|
||||
= c.footer do
|
||||
.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')
|
||||
.col-md-4.gl-mb-6
|
||||
= 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/runner_details'
|
||||
|
||||
- title = "##{@runner.id} (#{@runner.short_sha})"
|
||||
- breadcrumb_title title
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
- add_page_specific_style 'page_bundles/runner_details'
|
||||
|
||||
- add_to_breadcrumbs _('Runners'), group_runners_path(@group)
|
||||
|
||||
- if Feature.enabled?(:group_runner_view_ui, @group)
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
token: @resource_access_token,
|
||||
scopes: @scopes,
|
||||
access_levels: GroupMember.access_level_roles,
|
||||
default_access_level: Gitlab::Access::MAINTAINER,
|
||||
default_access_level: Gitlab::Access::GUEST,
|
||||
prefix: :resource_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
|
||||
%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
|
||||
- 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
|
||||
- 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
|
||||
- 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 }
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
token: @resource_access_token,
|
||||
scopes: @scopes,
|
||||
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,
|
||||
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/reports.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_discover.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;
|
||||
|
||||
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 (
|
||||
id bigint NOT NULL,
|
||||
member_id bigint NOT NULL,
|
||||
|
@ -17034,7 +17052,8 @@ CREATE TABLE members (
|
|||
override boolean DEFAULT false NOT NULL,
|
||||
state smallint DEFAULT 0,
|
||||
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
|
||||
|
@ -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 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 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
|
||||
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
|
||||
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_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 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_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_requested_at ON members USING btree (requested_at);
|
||||
|
@ -31996,6 +32024,9 @@ ALTER TABLE ONLY dast_scanner_profiles_builds
|
|||
ALTER TABLE ONLY issue_assignees
|
||||
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
|
||||
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
|
||||
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
|
||||
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="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="cirunnerupgradestatustypeunknown"></a>`UNKNOWN` | Upgrade status is unknown. |
|
||||
|
||||
### `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
|
||||
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:
|
||||
|
||||
- The last maintainer to review intended to start the merge and did not, OR
|
||||
- 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.
|
||||
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
|
||||
[Change Management Controls](https://about.gitlab.com/handbook/engineering/security/security-assurance/security-compliance/guidance/change-management.html).
|
||||
|
||||
When ready to merge:
|
||||
|
||||
|
|
|
@ -64,7 +64,6 @@ are very appreciative of the work done by translators and proofreaders!
|
|||
- German
|
||||
- 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)
|
||||
- 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)
|
||||
- Greek
|
||||
- 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 |
|
||||
| [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 |
|
||||
| [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 |
|
||||
| [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),
|
||||
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
|
||||
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
|
||||
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 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.
|
||||
|
||||
## 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
|
||||
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
|
||||
|
||||
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
|
||||
list_user_preferences: :gitlab_main
|
||||
loose_foreign_keys_deleted_records: :gitlab_shared
|
||||
member_roles: :gitlab_main
|
||||
member_tasks: :gitlab_main
|
||||
members: :gitlab_main
|
||||
merge_request_assignees: :gitlab_main
|
||||
|
|
|
@ -748,6 +748,9 @@ msgstr ""
|
|||
msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{labelStart}Project:%{labelEnd} %{project}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ RSpec.describe 'Issue Sidebar' do
|
|||
|
||||
page.within '.dropdown-menu-user' do
|
||||
expect(page).not_to have_content 'Unassigned'
|
||||
click_link user2.name
|
||||
click_button user2.name
|
||||
end
|
||||
|
||||
find('.participants').click
|
||||
|
|
|
@ -285,7 +285,7 @@ RSpec.describe "Issues > User edits issue", :js do
|
|||
end
|
||||
|
||||
page.within '.dropdown-menu-user' do
|
||||
click_link user.name
|
||||
click_button user.name
|
||||
end
|
||||
|
||||
page.within('.assignee') do
|
||||
|
@ -306,7 +306,7 @@ RSpec.describe "Issues > User edits issue", :js do
|
|||
|
||||
click_button('Edit')
|
||||
wait_for_requests
|
||||
click_link user.name
|
||||
click_button user.name
|
||||
|
||||
find('[data-testid="title"]').click
|
||||
wait_for_requests
|
||||
|
|
|
@ -92,10 +92,8 @@ RSpec.describe Ci::RunnersFinder do
|
|||
context 'set to an invalid value' do
|
||||
let(:upgrade_status) { :some_invalid_status }
|
||||
|
||||
it 'does not call with_upgrade_status' do
|
||||
expect(Ci::Runner).not_to receive(:with_upgrade_status)
|
||||
|
||||
expect(execute).to match_array(Ci::Runner.all)
|
||||
it 'raises ArgumentError' do
|
||||
expect { execute }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -11,22 +11,17 @@ exports[`~/access_tokens/components/expires_at_field should render datepicker wi
|
|||
arialabel=""
|
||||
autocomplete=""
|
||||
container=""
|
||||
data-qa-selector="expiry_date_field"
|
||||
defaultdate="Wed Aug 05 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
|
||||
displayfield="true"
|
||||
firstday="0"
|
||||
inputid="personal_access_token_expires_at"
|
||||
inputlabel="Enter date"
|
||||
inputname="personal_access_token[expires_at]"
|
||||
mindate="Mon Jul 06 2020 00:00:00 GMT+0000 (Greenwich Mean Time)"
|
||||
placeholder="YYYY-MM-DD"
|
||||
showclearbutton="true"
|
||||
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>
|
||||
`;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { shallowMount } from '@vue/test-utils';
|
||||
import { GlDatepicker } from '@gitlab/ui';
|
||||
import ExpiresAtField from '~/access_tokens/components/expires_at_field.vue';
|
||||
import { getDateInFuture } from '~/lib/utils/datetime_utility';
|
||||
|
||||
describe('~/access_tokens/components/expires_at_field', () => {
|
||||
let wrapper;
|
||||
|
@ -49,4 +50,12 @@ describe('~/access_tokens/components/expires_at_field', () => {
|
|||
|
||||
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>](https://www.gitlab.com%3E)'} | ${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</code>'} | ${'<code>code modified</code>'} | ${defaultEditAction}
|
||||
|
|
|
@ -561,10 +561,14 @@ describe('URL utility', () => {
|
|||
${'../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/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'}
|
||||
${'../john.md'} | ${'///bar/baz/foo.php'} | ${'/bar/john.md'}
|
||||
${'/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'}
|
||||
`(
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { GlEmptyState, GlBadge, GlTabs, GlTab } from '@gitlab/ui';
|
||||
import Vue, { nextTick } from 'vue';
|
||||
import Vue from 'vue';
|
||||
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
@ -303,6 +303,8 @@ describe('PackagesApp', () => {
|
|||
});
|
||||
|
||||
describe('deleting a file', () => {
|
||||
let showDeleteFileSpy;
|
||||
let showDeletePackageSpy;
|
||||
const [fileToDelete] = packageFiles();
|
||||
|
||||
const doDeleteFile = () => {
|
||||
|
@ -313,16 +315,48 @@ describe('PackagesApp', () => {
|
|||
return waitForPromises();
|
||||
};
|
||||
|
||||
it('opens a confirmation modal', async () => {
|
||||
it('opens delete file confirmation modal', async () => {
|
||||
createComponent();
|
||||
|
||||
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);
|
||||
|
||||
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 () => {
|
||||
|
|
|
@ -343,6 +343,14 @@ export const issuableQueryResponse = {
|
|||
__typename: 'Issue',
|
||||
id: 'gid://gitlab/Issue/1',
|
||||
iid: '1',
|
||||
author: {
|
||||
id: '1',
|
||||
avatarUrl: '/avatar',
|
||||
name: 'root',
|
||||
username: 'root',
|
||||
webUrl: 'root',
|
||||
status: null,
|
||||
},
|
||||
assignees: {
|
||||
nodes: [
|
||||
{
|
||||
|
@ -450,7 +458,7 @@ export const subscriptionResponse = {
|
|||
},
|
||||
};
|
||||
|
||||
const mockUser1 = {
|
||||
export const mockUser1 = {
|
||||
__typename: 'UserCore',
|
||||
id: 'gid://gitlab/User/1',
|
||||
avatarUrl:
|
||||
|
@ -459,6 +467,7 @@ const mockUser1 = {
|
|||
username: 'root',
|
||||
webUrl: '/root',
|
||||
status: null,
|
||||
canMerge: false,
|
||||
};
|
||||
|
||||
export const mockUser2 = {
|
||||
|
@ -469,6 +478,7 @@ export const mockUser2 = {
|
|||
username: 'rookie',
|
||||
webUrl: 'rookie',
|
||||
status: null,
|
||||
canMerge: false,
|
||||
};
|
||||
|
||||
export const searchResponse = {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue