Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-27 18:12:02 +00:00
parent 2977cf67ec
commit 3b060a68f3
145 changed files with 721 additions and 482 deletions

View File

@ -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"
data-qa-selector="expiry_date_field"
/>
</gl-datepicker>
<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"
/>
<template #description>
<max-expiration-date-message :max-date="maxDate" />
</template>

View File

@ -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);
}

View File

@ -208,8 +208,12 @@ export default {
},
handleFileDelete(file) {
this.track(REQUEST_DELETE_PACKAGE_FILE_TRACKING_ACTION);
this.fileToDelete = { ...file };
this.$refs.deleteFileModal.show();
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);

View File

@ -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>

View File

@ -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>

View File

@ -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"

View File

@ -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"
>
<template #meta>
<gl-icon
v-if="hasCannotMergeIcon"
name="warning-solid"
aria-hidden="true"
class="merge-icon"
:size="12"
/>
</template>
</gl-avatar-labeled>
</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 sidebar-participant"
>
<template #meta>
<gl-icon
v-if="hasCannotMergeIcon"
name="warning-solid"
aria-hidden="true"
class="merge-icon gl-left-6 gl-bottom-0"
:size="12"
/>
</template>
</gl-avatar-labeled>
</template>

View File

@ -8,6 +8,10 @@ query issueAssignees($fullPath: ID!, $iid: String!) {
issuable: issue(iid: $iid) {
__typename
id
author {
...User
...UserAvailability
}
assignees {
nodes {
...User

View File

@ -6,6 +6,13 @@ query getMrAssignees($fullPath: ID!, $iid: String!) {
id
issuable: mergeRequest(iid: $iid) {
id
author {
...User
...UserAvailability
mergeRequestInteraction {
canMerge
}
}
assignees {
nodes {
...User

View File

@ -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"

View File

@ -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 {

View File

@ -0,0 +1,3 @@
.runner-details-grid-template {
grid-template-columns: auto 1fr;
}

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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)

View File

@ -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|

View File

@ -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

View File

@ -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)

View File

@ -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')

View File

@ -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 }

View File

@ -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')

View File

@ -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"

10
db/docs/member_roles.yml Normal file
View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
78bb335a94237bfb5c5401807c9fc5e8ff9ec331af0ca0d3c5626253af5cde3f

View File

@ -0,0 +1 @@
179c400efd7d31b78b4314104c5e9cbdf744c5e2966cecf724d1b7088b515fd1

View File

@ -0,0 +1 @@
431392f5f88f493371b77263bbe380d08e486e8ba0d013213e6fe7fdcda3c7db

View File

@ -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;

View File

@ -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`

View File

@ -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:

View File

@ -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.

View File

@ -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:2021Broken 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)

View File

@ -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

View File

@ -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

View File

@ -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}**).

View File

@ -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

View File

@ -748,6 +748,9 @@ msgstr ""
msgid "%{labelStart}Namespace:%{labelEnd} %{namespace}"
msgstr ""
msgid "%{labelStart}Project:%{labelEnd} %{project}"
msgstr ""
msgid "%{labelStart}Scanner:%{labelEnd} %{scanner}"
msgstr ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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>
`;

View File

@ -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);
});
});

View File

@ -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}

View File

@ -555,18 +555,22 @@ describe('URL utility', () => {
describe('relativePathToAbsolute', () => {
it.each`
path | base | result
${'./foo'} | ${'bar/'} | ${'/bar/foo'}
${'../john.md'} | ${'bar/baz/foo.php'} | ${'/bar/john.md'}
${'../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'}
${'../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/user/images/img.png'}
${'../images/Image 1.png'} | ${'https://gitlab.com/user/project/'} | ${'https://gitlab.com/user/images/Image%201.png'}
path | base | result
${'./foo'} | ${'bar/'} | ${'/bar/foo'}
${'../john.md'} | ${'bar/baz/foo.php'} | ${'/bar/john.md'}
${'../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'}
`(
'converts relative path "$path" with base "$base" to absolute path => "expected"',
({ path, base, result }) => {

View File

@ -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 () => {

View File

@ -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