Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
df8a23a4f8
commit
813226e2ba
4
Gemfile
4
Gemfile
|
@ -154,7 +154,7 @@ gem 'html-pipeline', '~> 2.13.2'
|
|||
gem 'deckar01-task_list', '2.3.1'
|
||||
gem 'gitlab-markup', '~> 1.7.1'
|
||||
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
|
||||
gem 'commonmarker', '~> 0.21'
|
||||
gem 'commonmarker', '~> 0.23.2'
|
||||
gem 'kramdown', '~> 2.3.1'
|
||||
gem 'RedCloth', '~> 4.3.2'
|
||||
gem 'rdoc', '~> 6.3.2'
|
||||
|
@ -474,7 +474,7 @@ end
|
|||
gem 'spamcheck', '~> 0.1.0'
|
||||
|
||||
# Gitaly GRPC protocol definitions
|
||||
gem 'gitaly', '~> 14.3.0.pre.rc1'
|
||||
gem 'gitaly', '~> 14.3.0.pre.rc2'
|
||||
|
||||
# KAS GRPC protocol definitions
|
||||
gem 'kas-grpc', '~> 0.0.2'
|
||||
|
|
11
Gemfile.lock
11
Gemfile.lock
|
@ -200,8 +200,7 @@ GEM
|
|||
open4 (~> 1.3)
|
||||
coderay (1.1.3)
|
||||
colored2 (3.1.2)
|
||||
commonmarker (0.21.0)
|
||||
ruby-enum (~> 0.5)
|
||||
commonmarker (0.23.2)
|
||||
concurrent-ruby (1.1.9)
|
||||
connection_pool (2.2.2)
|
||||
contracts (0.11.0)
|
||||
|
@ -453,7 +452,7 @@ GEM
|
|||
rails (>= 3.2.0)
|
||||
git (1.7.0)
|
||||
rchardet (~> 1.8)
|
||||
gitaly (14.3.0.pre.rc1)
|
||||
gitaly (14.3.0.pre.rc2)
|
||||
grpc (~> 1.0)
|
||||
github-markup (1.7.0)
|
||||
gitlab (4.16.1)
|
||||
|
@ -1116,8 +1115,6 @@ GEM
|
|||
rubocop-rspec (1.44.1)
|
||||
rubocop (~> 0.87)
|
||||
rubocop-ast (>= 0.7.1)
|
||||
ruby-enum (0.8.0)
|
||||
i18n
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-magic (0.4.0)
|
||||
|
@ -1414,7 +1411,7 @@ DEPENDENCIES
|
|||
capybara-screenshot (~> 1.0.22)
|
||||
carrierwave (~> 1.3)
|
||||
charlock_holmes (~> 0.7.7)
|
||||
commonmarker (~> 0.21)
|
||||
commonmarker (~> 0.23.2)
|
||||
concurrent-ruby (~> 1.1)
|
||||
connection_pool (~> 2.0)
|
||||
countries (~> 3.0)
|
||||
|
@ -1464,7 +1461,7 @@ DEPENDENCIES
|
|||
gettext (~> 3.3)
|
||||
gettext_i18n_rails (~> 1.8.0)
|
||||
gettext_i18n_rails_js (~> 1.3)
|
||||
gitaly (~> 14.3.0.pre.rc1)
|
||||
gitaly (~> 14.3.0.pre.rc2)
|
||||
github-markup (~> 1.7.0)
|
||||
gitlab-chronic (~> 0.10.5)
|
||||
gitlab-dangerfiles (~> 2.3.0)
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
<script>
|
||||
import { GlAlert, GlFormGroup, GlFormInputGroup, GlSprintf } from '@gitlab/ui';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { numberToHumanSize } from '~/lib/utils/number_utils';
|
||||
import { __ } from '~/locale';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlFormGroup,
|
||||
GlAlert,
|
||||
GlFormInputGroup,
|
||||
GlSprintf,
|
||||
ClipboardButton,
|
||||
TitleArea,
|
||||
},
|
||||
inject: ['groupPath', 'dependencyProxyAvailable'],
|
||||
i18n: {
|
||||
subTitle: __(
|
||||
'Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies.',
|
||||
),
|
||||
proxyNotAvailableText: __('Dependency proxy feature is limited to public groups for now.'),
|
||||
proxyImagePrefix: __('Dependency proxy image prefix'),
|
||||
copyImagePrefixText: __('Copy prefix'),
|
||||
blobCountAndSize: __('Contains %{count} blobs of images (%{size})'),
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dependencyProxyTotalSize: 0,
|
||||
dependencyProxyImagePrefix: '',
|
||||
dependencyProxyBlobCount: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
infoMessages() {
|
||||
return [
|
||||
{
|
||||
text: this.$options.i18n.subTitle,
|
||||
link: helpPagePath('user/packages/dependency_proxy/index'),
|
||||
},
|
||||
];
|
||||
},
|
||||
humanizedTotalSize() {
|
||||
return numberToHumanSize(this.dependencyProxyTotalSize);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<title-area :title="__('Dependency Proxy')" :info-messages="infoMessages" />
|
||||
<gl-alert v-if="!dependencyProxyAvailable" :dismissible="false">
|
||||
{{ $options.i18n.proxyNotAvailableText }}
|
||||
</gl-alert>
|
||||
|
||||
<div v-else data-testid="main-area">
|
||||
<gl-form-group :label="$options.i18n.proxyImagePrefix">
|
||||
<gl-form-input-group
|
||||
readonly
|
||||
:value="dependencyProxyImagePrefix"
|
||||
class="gl-layout-w-limited"
|
||||
>
|
||||
<template #append>
|
||||
<clipboard-button
|
||||
:text="dependencyProxyImagePrefix"
|
||||
:title="$options.i18n.copyImagePrefixText"
|
||||
/>
|
||||
</template>
|
||||
</gl-form-input-group>
|
||||
<template #description>
|
||||
<span data-qa-selector="dependency_proxy_count" data-testid="proxy-count">
|
||||
<gl-sprintf :message="$options.i18n.blobCountAndSize">
|
||||
<template #count>{{ dependencyProxyBlobCount }}</template>
|
||||
<template #size>{{ humanizedTotalSize }}</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
</template>
|
||||
</gl-form-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,26 @@
|
|||
import Vue from 'vue';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import app from '~/packages_and_registries/dependency_proxy/app.vue';
|
||||
import { apolloProvider } from '~/packages_and_registries/package_registry/graphql';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
|
||||
Vue.use(Translate);
|
||||
|
||||
export const initDependencyProxyApp = () => {
|
||||
const el = document.getElementById('js-dependency-proxy');
|
||||
if (!el) {
|
||||
return null;
|
||||
}
|
||||
const { dependencyProxyAvailable, ...dataset } = el.dataset;
|
||||
return new Vue({
|
||||
el,
|
||||
apolloProvider,
|
||||
provide: {
|
||||
dependencyProxyAvailable: parseBoolean(dependencyProxyAvailable),
|
||||
...dataset,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement(app);
|
||||
},
|
||||
});
|
||||
};
|
|
@ -4,6 +4,7 @@ import {
|
|||
GlButtonGroup,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownText,
|
||||
GlDropdownSectionHeader,
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
|
@ -20,6 +21,7 @@ export default {
|
|||
GlButtonGroup,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownText,
|
||||
GlDropdownSectionHeader,
|
||||
GlLoadingIcon,
|
||||
GlSearchBoxByType,
|
||||
|
@ -57,8 +59,20 @@ export default {
|
|||
userNamespace() {
|
||||
return this.currentUser.namespace || {};
|
||||
},
|
||||
hasGroupMatches() {
|
||||
return this.userGroups.length;
|
||||
},
|
||||
hasNamespaceMatches() {
|
||||
return this.userNamespace.fullPath?.toLowerCase().includes(this.search.toLowerCase());
|
||||
},
|
||||
hasNoMatches() {
|
||||
return !this.hasGroupMatches && !this.hasNamespaceMatches;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
focusInput() {
|
||||
this.$refs.search.focusInput();
|
||||
},
|
||||
handleClick({ id, fullPath }) {
|
||||
this.selectedNamespace = {
|
||||
id: getIdFromGraphQLId(id),
|
||||
|
@ -78,18 +92,24 @@ export default {
|
|||
toggle-class="gl-rounded-top-right-base! gl-rounded-bottom-right-base!"
|
||||
data-qa-selector="select_namespace_dropdown"
|
||||
@show="track('activate_form_input', { label: trackLabel, property: 'project_path' })"
|
||||
@shown="focusInput"
|
||||
>
|
||||
<gl-search-box-by-type v-model.trim="search" />
|
||||
<gl-search-box-by-type ref="search" v-model.trim="search" />
|
||||
<gl-loading-icon v-if="$apollo.queries.currentUser.loading" />
|
||||
<template v-else>
|
||||
<gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item v-for="group of userGroups" :key="group.id" @click="handleClick(group)">
|
||||
{{ group.fullPath }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item @click="handleClick(userNamespace)">
|
||||
{{ userNamespace.fullPath }}
|
||||
</gl-dropdown-item>
|
||||
<template v-if="hasGroupMatches">
|
||||
<gl-dropdown-section-header>{{ __('Groups') }}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item v-for="group of userGroups" :key="group.id" @click="handleClick(group)">
|
||||
{{ group.fullPath }}
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
<template v-if="hasNamespaceMatches">
|
||||
<gl-dropdown-section-header>{{ __('Users') }}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item @click="handleClick(userNamespace)">
|
||||
{{ userNamespace.fullPath }}
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
<gl-dropdown-text v-if="hasNoMatches">{{ __('No matches found') }}</gl-dropdown-text>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
import { escape, find, countBy } from 'lodash';
|
||||
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
|
||||
import createFlash from '~/flash';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { n__, s__, __, sprintf } from '~/locale';
|
||||
import { getUsers, getGroups, getDeployKeys } from './api/access_dropdown_api';
|
||||
import { LEVEL_TYPES, LEVEL_ID_PROP, ACCESS_LEVELS, ACCESS_LEVEL_NONE } from './constants';
|
||||
|
||||
export default class AccessDropdown {
|
||||
|
@ -16,9 +16,6 @@ export default class AccessDropdown {
|
|||
this.accessLevelsData = accessLevelsData.roles;
|
||||
this.$dropdown = $dropdown;
|
||||
this.$wrap = this.$dropdown.closest(`.${this.accessLevel}-container`);
|
||||
this.usersPath = '/-/autocomplete/users.json';
|
||||
this.groupsPath = '/-/autocomplete/project_groups.json';
|
||||
this.deployKeysPath = '/-/autocomplete/deploy_keys_with_owners.json';
|
||||
this.defaultLabel = this.$dropdown.data('defaultLabel');
|
||||
|
||||
this.setSelectedItems([]);
|
||||
|
@ -318,9 +315,9 @@ export default class AccessDropdown {
|
|||
getData(query, callback) {
|
||||
if (this.hasLicense) {
|
||||
Promise.all([
|
||||
this.getDeployKeys(query),
|
||||
this.getUsers(query),
|
||||
this.groupsData ? Promise.resolve(this.groupsData) : this.getGroups(),
|
||||
getDeployKeys(query),
|
||||
getUsers(query),
|
||||
this.groupsData ? Promise.resolve(this.groupsData) : getGroups(),
|
||||
])
|
||||
.then(([deployKeysResponse, usersResponse, groupsResponse]) => {
|
||||
this.groupsData = groupsResponse;
|
||||
|
@ -332,7 +329,7 @@ export default class AccessDropdown {
|
|||
createFlash({ message: __('Failed to load groups, users and deploy keys.') });
|
||||
});
|
||||
} else {
|
||||
this.getDeployKeys(query)
|
||||
getDeployKeys(query)
|
||||
.then((deployKeysResponse) => callback(this.consolidateData(deployKeysResponse.data)))
|
||||
.catch(() => createFlash({ message: __('Failed to load deploy keys.') }));
|
||||
}
|
||||
|
@ -473,46 +470,6 @@ export default class AccessDropdown {
|
|||
return consolidatedData;
|
||||
}
|
||||
|
||||
getUsers(query) {
|
||||
return axios.get(this.buildUrl(gon.relative_url_root, this.usersPath), {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
active: true,
|
||||
project_id: gon.current_project_id,
|
||||
push_code: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
return axios.get(this.buildUrl(gon.relative_url_root, this.groupsPath), {
|
||||
params: {
|
||||
project_id: gon.current_project_id,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getDeployKeys(query) {
|
||||
return axios.get(this.buildUrl(gon.relative_url_root, this.deployKeysPath), {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
active: true,
|
||||
project_id: gon.current_project_id,
|
||||
push_code: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
buildUrl(urlRoot, url) {
|
||||
let newUrl;
|
||||
if (urlRoot != null) {
|
||||
newUrl = urlRoot.replace(/\/$/, '') + url;
|
||||
}
|
||||
return newUrl;
|
||||
}
|
||||
|
||||
renderRow(item) {
|
||||
let criteria = {};
|
||||
let groupRowEl;
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
const USERS_PATH = '/-/autocomplete/users.json';
|
||||
const GROUPS_PATH = '/-/autocomplete/project_groups.json';
|
||||
const DEPLOY_KEYS_PATH = '/-/autocomplete/deploy_keys_with_owners.json';
|
||||
|
||||
const buildUrl = (urlRoot, url) => {
|
||||
let newUrl;
|
||||
if (urlRoot != null) {
|
||||
newUrl = urlRoot.replace(/\/$/, '') + url;
|
||||
}
|
||||
return newUrl;
|
||||
};
|
||||
|
||||
export const getUsers = (query) => {
|
||||
return axios.get(buildUrl(gon.relative_url_root || '', USERS_PATH), {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
active: true,
|
||||
project_id: gon.current_project_id,
|
||||
push_code: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getGroups = () => {
|
||||
return axios.get(buildUrl(gon.relative_url_root || '', GROUPS_PATH), {
|
||||
params: {
|
||||
project_id: gon.current_project_id,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeployKeys = (query) => {
|
||||
return axios.get(buildUrl(gon.relative_url_root || '', DEPLOY_KEYS_PATH), {
|
||||
params: {
|
||||
search: query,
|
||||
per_page: 20,
|
||||
active: true,
|
||||
project_id: gon.current_project_id,
|
||||
push_code: true,
|
||||
},
|
||||
});
|
||||
};
|
|
@ -0,0 +1,292 @@
|
|||
<script>
|
||||
import {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownSectionHeader,
|
||||
GlDropdownDivider,
|
||||
GlSearchBoxByType,
|
||||
GlLoadingIcon,
|
||||
GlAvatar,
|
||||
GlSprintf,
|
||||
} from '@gitlab/ui';
|
||||
import { debounce } from 'lodash';
|
||||
import createFlash from '~/flash';
|
||||
import { __, s__, n__ } from '~/locale';
|
||||
import { getUsers, getGroups, getDeployKeys } from '../api/access_dropdown_api';
|
||||
import { LEVEL_TYPES, ACCESS_LEVELS } from '../constants';
|
||||
|
||||
export const i18n = {
|
||||
selectUsers: s__('ProtectedEnvironment|Select users'),
|
||||
rolesSectionHeader: s__('AccessDropdown|Roles'),
|
||||
groupsSectionHeader: s__('AccessDropdown|Groups'),
|
||||
usersSectionHeader: s__('AccessDropdown|Users'),
|
||||
deployKeysSectionHeader: s__('AccessDropdown|Deploy Keys'),
|
||||
ownedBy: __('Owned by %{image_tag}'),
|
||||
};
|
||||
|
||||
export default {
|
||||
i18n,
|
||||
components: {
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownSectionHeader,
|
||||
GlDropdownDivider,
|
||||
GlSearchBoxByType,
|
||||
GlLoadingIcon,
|
||||
GlAvatar,
|
||||
GlSprintf,
|
||||
},
|
||||
props: {
|
||||
accessLevelsData: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
accessLevel: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
hasLicense: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
query: '',
|
||||
users: [],
|
||||
groups: [],
|
||||
roles: [],
|
||||
deployKeys: [],
|
||||
selected: {
|
||||
[LEVEL_TYPES.GROUP]: [],
|
||||
[LEVEL_TYPES.USER]: [],
|
||||
[LEVEL_TYPES.ROLE]: [],
|
||||
[LEVEL_TYPES.DEPLOY_KEY]: [],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showDeployKeys() {
|
||||
return this.accessLevel === ACCESS_LEVELS.PUSH && this.deployKeys.length;
|
||||
},
|
||||
toggleLabel() {
|
||||
const counts = Object.entries(this.selected).reduce((acc, [key, value]) => {
|
||||
acc[key] = value.length;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const isOnlyRoleSelected =
|
||||
counts[LEVEL_TYPES.ROLE] === 1 &&
|
||||
[counts[LEVEL_TYPES.USER], counts[LEVEL_TYPES.GROUP], counts[LEVEL_TYPES.DEPLOY_KEY]].every(
|
||||
(count) => count === 0,
|
||||
);
|
||||
|
||||
if (isOnlyRoleSelected) {
|
||||
return this.selected[LEVEL_TYPES.ROLE][0].text;
|
||||
}
|
||||
|
||||
const labelPieces = [];
|
||||
|
||||
if (counts[LEVEL_TYPES.ROLE] > 0) {
|
||||
labelPieces.push(n__('1 role', '%d roles', counts[LEVEL_TYPES.ROLE]));
|
||||
}
|
||||
|
||||
if (counts[LEVEL_TYPES.USER] > 0) {
|
||||
labelPieces.push(n__('1 user', '%d users', counts[LEVEL_TYPES.USER]));
|
||||
}
|
||||
|
||||
if (counts[LEVEL_TYPES.DEPLOY_KEY] > 0) {
|
||||
labelPieces.push(n__('1 deploy key', '%d deploy keys', counts[LEVEL_TYPES.DEPLOY_KEY]));
|
||||
}
|
||||
|
||||
if (counts[LEVEL_TYPES.GROUP] > 0) {
|
||||
labelPieces.push(n__('1 group', '%d groups', counts[LEVEL_TYPES.GROUP]));
|
||||
}
|
||||
|
||||
return labelPieces.join(', ') || i18n.selectUsers;
|
||||
},
|
||||
toggleClass() {
|
||||
return this.toggleLabel === i18n.selectUsers ? 'gl-text-gray-500!' : '';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
query: debounce(function debouncedSearch() {
|
||||
return this.getData();
|
||||
}, 500),
|
||||
},
|
||||
created() {
|
||||
this.getData();
|
||||
},
|
||||
|
||||
methods: {
|
||||
focusInput() {
|
||||
this.$refs.search.focusInput();
|
||||
},
|
||||
getData() {
|
||||
this.loading = true;
|
||||
|
||||
if (this.hasLicense) {
|
||||
Promise.all([
|
||||
getDeployKeys(this.query),
|
||||
getUsers(this.query),
|
||||
this.groups.length ? Promise.resolve({ data: this.groups }) : getGroups(),
|
||||
])
|
||||
.then(([deployKeysResponse, usersResponse, groupsResponse]) =>
|
||||
this.consolidateData(deployKeysResponse.data, usersResponse.data, groupsResponse.data),
|
||||
)
|
||||
.catch(() =>
|
||||
createFlash({ message: __('Failed to load groups, users and deploy keys.') }),
|
||||
)
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
getDeployKeys(this.query)
|
||||
.then((deployKeysResponse) => this.consolidateData(deployKeysResponse.data))
|
||||
.catch(() => createFlash({ message: __('Failed to load deploy keys.') }))
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
consolidateData(deployKeysResponse, usersResponse = [], groupsResponse = []) {
|
||||
// This re-assignment is intentional as level.type property is being used for comparision,
|
||||
// and accessLevelsData is provided by gon.create_access_levels which doesn't have `type` included.
|
||||
// See this discussion https://gitlab.com/gitlab-org/gitlab/merge_requests/1629#note_31285823
|
||||
this.roles = this.accessLevelsData.map((role) => ({ ...role, type: LEVEL_TYPES.ROLE }));
|
||||
|
||||
if (this.hasLicense) {
|
||||
this.groups = groupsResponse.map((group) => ({ ...group, type: LEVEL_TYPES.GROUP }));
|
||||
this.users = usersResponse.map((user) => ({ ...user, type: LEVEL_TYPES.USER }));
|
||||
}
|
||||
|
||||
this.deployKeys = deployKeysResponse.map((response) => {
|
||||
const {
|
||||
id,
|
||||
fingerprint,
|
||||
title,
|
||||
owner: { avatar_url, name, username },
|
||||
} = response;
|
||||
|
||||
const shortFingerprint = `(${fingerprint.substring(0, 14)}...)`;
|
||||
|
||||
return {
|
||||
id,
|
||||
title: title.concat(' ', shortFingerprint),
|
||||
avatar_url,
|
||||
fullname: name,
|
||||
username,
|
||||
type: LEVEL_TYPES.DEPLOY_KEY,
|
||||
};
|
||||
});
|
||||
},
|
||||
onItemClick(item) {
|
||||
this.toggleSelection(this.selected[item.type], item);
|
||||
this.emitUpdate();
|
||||
},
|
||||
toggleSelection(arr, item) {
|
||||
const itemIndex = arr.indexOf(item);
|
||||
if (itemIndex > -1) {
|
||||
arr.splice(itemIndex, 1);
|
||||
} else arr.push(item);
|
||||
},
|
||||
isSelected(item) {
|
||||
return this.selected[item.type].some((selected) => selected.id === item.id);
|
||||
},
|
||||
emitUpdate() {
|
||||
const selected = Object.values(this.selected).flat();
|
||||
this.$emit('select', selected);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<gl-dropdown
|
||||
:text="toggleLabel"
|
||||
class="gl-display-block"
|
||||
:toggle-class="toggleClass"
|
||||
aria-labelledby="allowed-users-label"
|
||||
@shown="focusInput"
|
||||
>
|
||||
<template #header>
|
||||
<gl-search-box-by-type ref="search" v-model.trim="query" />
|
||||
<gl-loading-icon v-if="loading" size="sm" />
|
||||
</template>
|
||||
<template v-if="roles.length">
|
||||
<gl-dropdown-section-header>{{
|
||||
$options.i18n.rolesSectionHeader
|
||||
}}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item
|
||||
v-for="role in roles"
|
||||
:key="role.id"
|
||||
is-check-item
|
||||
:is-checked="isSelected(role)"
|
||||
@click.native.capture.stop="onItemClick(role)"
|
||||
>
|
||||
{{ role.text }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider v-if="groups.length || users.length || showDeployKeys" />
|
||||
</template>
|
||||
|
||||
<template v-if="groups.length">
|
||||
<gl-dropdown-section-header>{{
|
||||
$options.i18n.groupsSectionHeader
|
||||
}}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item
|
||||
v-for="group in groups"
|
||||
:key="group.id"
|
||||
:avatar-url="group.avatar_url"
|
||||
is-check-item
|
||||
:is-checked="isSelected(group)"
|
||||
@click.native.capture.stop="onItemClick(group)"
|
||||
>
|
||||
{{ group.name }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider v-if="users.length || showDeployKeys" />
|
||||
</template>
|
||||
|
||||
<template v-if="users.length">
|
||||
<gl-dropdown-section-header>{{
|
||||
$options.i18n.usersSectionHeader
|
||||
}}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item
|
||||
v-for="user in users"
|
||||
:key="user.id"
|
||||
:avatar-url="user.avatar_url"
|
||||
:secondary-text="user.username"
|
||||
is-check-item
|
||||
:is-checked="isSelected(user)"
|
||||
@click.native.capture.stop="onItemClick(user)"
|
||||
>
|
||||
{{ user.name }}
|
||||
</gl-dropdown-item>
|
||||
<gl-dropdown-divider v-if="showDeployKeys" />
|
||||
</template>
|
||||
|
||||
<template v-if="showDeployKeys">
|
||||
<gl-dropdown-section-header>{{
|
||||
$options.i18n.deployKeysSectionHeader
|
||||
}}</gl-dropdown-section-header>
|
||||
<gl-dropdown-item
|
||||
v-for="key in deployKeys"
|
||||
:key="key.id"
|
||||
is-check-item
|
||||
:is-checked="isSelected(key)"
|
||||
class="gl-text-truncate"
|
||||
@click.native.capture.stop="onItemClick(key)"
|
||||
>
|
||||
<div class="gl-text-truncate gl-font-weight-bold">{{ key.title }}</div>
|
||||
<div class="gl-text-gray-700 gl-text-truncate">
|
||||
<gl-sprintf :message="$options.i18n.ownedBy">
|
||||
<template #image_tag>
|
||||
<gl-avatar :src="key.avatar_url" :size="24" />
|
||||
</template> </gl-sprintf
|
||||
>{{ key.fullname }} ({{ key.username }})
|
||||
</div>
|
||||
</gl-dropdown-item>
|
||||
</template>
|
||||
</gl-dropdown>
|
||||
</template>
|
|
@ -0,0 +1,28 @@
|
|||
import Vue from 'vue';
|
||||
import AccessDropdown from './components/access_dropdown.vue';
|
||||
|
||||
export const initAccessDropdown = (el, options) => {
|
||||
if (!el) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { accessLevelsData, accessLevel } = options;
|
||||
|
||||
return new Vue({
|
||||
el,
|
||||
render(createElement) {
|
||||
const vm = this;
|
||||
return createElement(AccessDropdown, {
|
||||
props: {
|
||||
accessLevel,
|
||||
accessLevelsData: accessLevelsData.roles,
|
||||
},
|
||||
on: {
|
||||
select(selected) {
|
||||
vm.$emit('select', selected);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
|
@ -1,13 +1,11 @@
|
|||
<script>
|
||||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { GlAlert } from '@gitlab/ui';
|
||||
import { slugifyWithUnderscore } from '~/lib/utils/text_utility';
|
||||
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlAlert,
|
||||
GlSprintf,
|
||||
GlLink,
|
||||
LocalStorageSync,
|
||||
},
|
||||
props: {
|
||||
|
@ -15,10 +13,6 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
feedbackLink: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -44,19 +38,8 @@ export default {
|
|||
<template>
|
||||
<div v-show="showAlert">
|
||||
<local-storage-sync v-model="isDismissed" :storage-key="storageKey" as-json />
|
||||
<gl-alert v-if="showAlert" class="gl-mt-5" @dismiss="dismissFeedbackAlert">
|
||||
<gl-sprintf
|
||||
:message="
|
||||
__(
|
||||
'Please share your feedback about %{featureName} %{linkStart}in this issue%{linkEnd} to help us improve the experience.',
|
||||
)
|
||||
"
|
||||
>
|
||||
<template #featureName>{{ featureName }}</template>
|
||||
<template #link="{ content }">
|
||||
<gl-link :href="feedbackLink" target="_blank">{{ content }}</gl-link>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<gl-alert v-if="showAlert" @dismiss="dismissFeedbackAlert">
|
||||
<slot></slot>
|
||||
</gl-alert>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -64,8 +64,8 @@ module Ci
|
|||
delegate :gitlab_deploy_token, to: :project
|
||||
delegate :trigger_short_token, to: :trigger_request, allow_nil: true
|
||||
|
||||
ignore_columns :id_convert_to_bigint, remove_with: '14.1', remove_after: '2021-07-22'
|
||||
ignore_columns :stage_id_convert_to_bigint, remove_with: '14.1', remove_after: '2021-07-22'
|
||||
ignore_columns :id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
|
||||
ignore_columns :stage_id_convert_to_bigint, remove_with: '14.5', remove_after: '2021-10-22'
|
||||
|
||||
##
|
||||
# Since Gitlab 11.5, deployments records started being created right after
|
||||
|
|
|
@ -27,8 +27,9 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
user,
|
||||
ref: schedule.ref)
|
||||
.execute!(:schedule, ignore_skip_ci: true, save_on_errors: false, schedule: schedule)
|
||||
rescue Ci::CreatePipelineService::CreateError
|
||||
# no-op. This is a user operation error such as corrupted .gitlab-ci.yml.
|
||||
rescue Ci::CreatePipelineService::CreateError => e
|
||||
# This is a user operation error such as corrupted .gitlab-ci.yml. Log the error for debugging purpose.
|
||||
log_extra_metadata_on_done(:pipeline_creation_error, e)
|
||||
rescue StandardError => e
|
||||
error(schedule, e)
|
||||
end
|
||||
|
@ -37,10 +38,16 @@ class RunPipelineScheduleWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
|
||||
def error(schedule, error)
|
||||
failed_creation_counter.increment
|
||||
log_error(schedule, error)
|
||||
track_error(schedule, error)
|
||||
end
|
||||
|
||||
def log_error(schedule, error)
|
||||
Gitlab::AppLogger.error "Failed to create a scheduled pipeline. " \
|
||||
"schedule_id: #{schedule.id} message: #{error.message}"
|
||||
end
|
||||
|
||||
def track_error(schedule, error)
|
||||
Gitlab::ErrorTracking
|
||||
.track_and_raise_for_dev_exception(error,
|
||||
issue_url: 'https://gitlab.com/gitlab-org/gitlab-foss/issues/41231',
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
- name: "NFS for Git repository storage deprecated" # The name of the feature to be deprecated
|
||||
announcement_milestone: "14.0" # The milestone when this feature was first announced as deprecated.
|
||||
announcement_date: "2021-06-22" # The date of the milestone release when this feature was first announced as deprecated
|
||||
removal_milestone: "15.2" # The milestone when this feature is planned to be removed
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
With the general availability of Gitaly Cluster ([introduced in GitLab 13.0](https://about.gitlab.com/releases/2020/05/22/gitlab-13-0-released/)), we have deprecated development (bugfixes, performance improvements, etc) for NFS for Git repository storage in GitLab 14.0. We will continue to provide technical support for NFS for Git repositories throughout 14.x, but we will remove all support for NFS in GitLab 15.0. Please see our official [Statement of Support](https://about.gitlab.com/support/statement-of-support.html#gitaly-and-nfs) for further information.
|
||||
|
||||
Gitaly Cluster offers tremendous benefits for our customers such as:
|
||||
|
||||
- [Variable replication factors](https://docs.gitlab.com/ee/administration/gitaly/praefect.html#replication-factor).
|
||||
- [Strong consistency](https://docs.gitlab.com/ee/administration/gitaly/praefect.html#strong-consistency).
|
||||
- [Distributed read capabilities](https://docs.gitlab.com/ee/administration/gitaly/praefect.html#distributed-reads).
|
||||
|
||||
We encourage customers currently using NFS for Git repositories to plan their migration by reviewing our documentation on [migrating to Gitaly Cluster](https://docs.gitlab.com/ee/administration/gitaly/praefect.html#migrate-to-gitaly-cluster).
|
||||
|
||||
stage: # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: # (optional) This is a link to the deprecation issue in GitLab
|
||||
documentation_url: # (optional) This is a link to the current documentation page
|
||||
image_url: # (optional) This is a link to a thumbnail image depicting the feature
|
||||
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
||||
removal_date: "2022-06-22" # (optional - may be required in the future) YYYY-MM-DD format - the date of the milestone release when this feature is planned to be removed
|
|
@ -0,0 +1,13 @@
|
|||
- name: "Release CLI be distributed as a generic package" # The name of the feature to be deprecated
|
||||
announcement_milestone: "14.2" # The milestone when this feature was first announced as deprecated.
|
||||
announcement_date: "2021-08-22" # The date of the milestone release when this feature was first announced as deprecated
|
||||
removal_milestone: "14.6" # The milestone when this feature is planned to be removed
|
||||
body: | # Do not modify this line, instead modify the lines below.
|
||||
The [release-cli](https://gitlab.com/gitlab-org/release-cli) will be released as a [generic package](https://gitlab.com/gitlab-org/release-cli/-/packages) starting in GitLab 14.2. We will continue to deploy it as a binary to S3 until GitLab 14.5 and stop distributing it in S3 in GitLab 14.6.
|
||||
stage: # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
|
||||
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
|
||||
issue_url: # (optional) This is a link to the deprecation issue in GitLab
|
||||
documentation_url: # (optional) This is a link to the current documentation page
|
||||
image_url: # (optional) This is a link to a thumbnail image depicting the feature
|
||||
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
|
||||
removal_date: "2021-12-22" # (optional - may be required in the future) YYYY-MM-DD format - the date of the milestone release when this feature is planned to be removed
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CleanupBigintConversionForCiBuilds < Gitlab::Database::Migration[1.0]
|
||||
enable_lock_retries!
|
||||
|
||||
TABLE = :ci_builds
|
||||
COLUMNS = [:id, :stage_id]
|
||||
|
||||
def up
|
||||
cleanup_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
|
||||
def down
|
||||
restore_conversion_of_integer_to_bigint(TABLE, COLUMNS)
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
ecb73d9a0be0cf04a8b405f1225d1a5de2fcdbb4c277fd5549f72a7a21596cdc
|
|
@ -77,16 +77,6 @@ RETURN NULL;
|
|||
END
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_3f6129be01d2() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW."id_convert_to_bigint" := NEW."id";
|
||||
NEW."stage_id_convert_to_bigint" := NEW."stage_id";
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE FUNCTION trigger_542d6c2ad72e() RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
|
@ -11337,7 +11327,6 @@ CREATE TABLE ci_build_trace_metadata (
|
|||
);
|
||||
|
||||
CREATE TABLE ci_builds (
|
||||
id_convert_to_bigint integer DEFAULT 0 NOT NULL,
|
||||
status character varying,
|
||||
finished_at timestamp without time zone,
|
||||
trace text,
|
||||
|
@ -11372,7 +11361,6 @@ CREATE TABLE ci_builds (
|
|||
coverage_regex character varying,
|
||||
auto_canceled_by_id integer,
|
||||
retried boolean,
|
||||
stage_id_convert_to_bigint integer,
|
||||
protected boolean,
|
||||
failure_reason integer,
|
||||
scheduled_at timestamp with time zone,
|
||||
|
@ -27366,8 +27354,6 @@ ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_p
|
|||
|
||||
ALTER INDEX product_analytics_events_experimental_pkey ATTACH PARTITION gitlab_partitions_static.product_analytics_events_experimental_63_pkey;
|
||||
|
||||
CREATE TRIGGER trigger_3f6129be01d2 BEFORE INSERT OR UPDATE ON ci_builds FOR EACH ROW EXECUTE FUNCTION trigger_3f6129be01d2();
|
||||
|
||||
CREATE TRIGGER trigger_542d6c2ad72e BEFORE INSERT OR UPDATE ON ci_builds_metadata FOR EACH ROW EXECUTE FUNCTION trigger_542d6c2ad72e();
|
||||
|
||||
CREATE TRIGGER trigger_8487d4de3e7b BEFORE INSERT OR UPDATE ON ci_builds_metadata FOR EACH ROW EXECUTE FUNCTION trigger_8487d4de3e7b();
|
||||
|
|
|
@ -114,7 +114,7 @@ MySQL/MariaDB are advised to [migrate to PostgreSQL](../update/mysql_to_postgres
|
|||
The server running PostgreSQL should have _at least_ 5-10 GB of storage
|
||||
available, though the exact requirements [depend on the number of users](../administration/reference_architectures/index.md).
|
||||
|
||||
We highly recommend using the minimum PostgreSQL versions (as specified in
|
||||
We highly recommend using at least the minimum PostgreSQL versions (as specified in
|
||||
the following table) as these were used for development and testing:
|
||||
|
||||
| GitLab version | Minimum PostgreSQL version |
|
||||
|
|
|
@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
type: index
|
||||
---
|
||||
|
||||
# Security partner integrations
|
||||
# Security partner integrations **(FREE)**
|
||||
|
||||
You can integrate GitLab with its security partners. This page has information on how do this with
|
||||
each security partner:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Manage
|
||||
group: Workspace
|
||||
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
|
||||
type: reference
|
||||
---
|
||||
|
|
|
@ -57,10 +57,10 @@ For safety reasons, you should maintain an up-to-date backup on your own if you
|
|||
Updating to major versions might need some manual intervention. For more information,
|
||||
check the version your are upgrading to:
|
||||
|
||||
- [GitLab 14](https://docs.gitlab.com/omnibus/gitlab_14_changes.html)
|
||||
- [GitLab 13](https://docs.gitlab.com/omnibus/gitlab_13_changes.html)
|
||||
- [GitLab 12](https://docs.gitlab.com/omnibus/gitlab_12_changes.html)
|
||||
- [GitLab 11](https://docs.gitlab.com/omnibus/gitlab_11_changes.html)
|
||||
- [GitLab 14](https://docs.gitlab.com/omnibus/update/gitlab_14_changes.html)
|
||||
- [GitLab 13](https://docs.gitlab.com/omnibus/update/gitlab_13_changes.html)
|
||||
- [GitLab 12](https://docs.gitlab.com/omnibus/update/gitlab_12_changes.html)
|
||||
- [GitLab 11](https://docs.gitlab.com/omnibus/update/gitlab_11_changes.html)
|
||||
|
||||
## Upgrade using the official repositories
|
||||
|
||||
|
|
|
@ -7,15 +7,14 @@ type: reference, howto
|
|||
|
||||
# Coverage-guided fuzz testing **(ULTIMATE)**
|
||||
|
||||
Coverage-guided fuzzing sends random inputs to an instrumented version of your application in an
|
||||
effort to cause unexpected behavior. Such behavior indicates a bug that you should address.
|
||||
GitLab allows you to add coverage-guided fuzz testing to your pipelines. This helps you discover
|
||||
bugs and potential security issues that other QA processes may miss. Coverage-guided fuzzing sends
|
||||
random inputs to an instrumented version of your application in an effort to cause unexpected
|
||||
behavior, such as a crash. Such behavior indicates a bug that you should address.
|
||||
bugs and potential security issues that other QA processes may miss.
|
||||
|
||||
We recommend that you use fuzz testing in addition to the other security scanners in [GitLab Secure](../index.md)
|
||||
and your own test processes. If you're using [GitLab CI/CD](../../../ci/index.md),
|
||||
you can run your coverage-guided fuzz tests as part your CI/CD workflow. You can take advantage of
|
||||
coverage-guided fuzzing by including the CI job in your existing `.gitlab-ci.yml` file.
|
||||
you can run your coverage-guided fuzz tests as part your CI/CD workflow.
|
||||
|
||||
## Supported fuzzing engines and languages
|
||||
|
||||
|
|
|
@ -102,8 +102,9 @@ This might involve reconfiguring your firewall to prevent blocking connection on
|
|||
|
||||
### Connect to the remote GitLab instance
|
||||
|
||||
1. Navigate to the New Group page, either via the `+` button in the top navigation bar, or the **New subgroup** button
|
||||
on an existing group's page.
|
||||
1. Go to the New Group page:
|
||||
- On the top bar, select `+` and then **New group**.
|
||||
- Or, on an existing group's page, in the top right, select **New subgroup**.
|
||||
|
||||
![Navigation paths to create a new group](img/new_group_navigation_v13_8.png)
|
||||
|
||||
|
@ -111,21 +112,18 @@ on an existing group's page.
|
|||
|
||||
![Fill in import details](img/import_panel_v14_1.png)
|
||||
|
||||
1. Fill in source URL of your GitLab.
|
||||
1. Fill in [personal access token](../../../user/profile/personal_access_tokens.md) for remote GitLab instance.
|
||||
1. Click "Connect instance".
|
||||
1. Enter the source URL of your GitLab instance.
|
||||
1. Enter the [personal access token](../../../user/profile/personal_access_tokens.md) for your remote GitLab instance.
|
||||
1. Select **Connect instance**.
|
||||
|
||||
### Selecting which groups to import
|
||||
|
||||
After you have authorized access to the GitLab instance, you are redirected to the GitLab Group
|
||||
Migration importer page. Listed are the remote GitLab groups to which you have the Owner role.
|
||||
Migration importer page. The remote groups you have the Owner role for are listed.
|
||||
|
||||
1. By default, the proposed group namespaces match the names as they exist in remote instance, but based on your permissions, you can choose to edit these names before you proceed to import any of them.
|
||||
|
||||
1. Select the **Import** button next to any number of groups.
|
||||
|
||||
1. The **Status** column shows the import status of each group. You can choose to leave the page open and it updates in real-time.
|
||||
|
||||
1. Once a group has been imported, click its GitLab path to open its GitLab URL.
|
||||
1. Next to the groups you want to import, select **Import**.
|
||||
1. The **Status** column shows the import status of each group. If you leave the page open, it updates in real-time.
|
||||
1. After a group has been imported, select its GitLab path to open its GitLab URL.
|
||||
|
||||
![Group Importer page](img/bulk_imports_v14_1.png)
|
||||
|
|
|
@ -77,6 +77,8 @@ To use the GitLab endpoint for NuGet Packages, choose an option:
|
|||
|
||||
Some features such as [publishing](#publish-a-nuget-package) a package are only available on the project-level endpoint.
|
||||
|
||||
When asking for versions of a given NuGet package name, the GitLab Package Registry returns a maximum of 300 most recent versions.
|
||||
|
||||
WARNING:
|
||||
Because of how NuGet handles credentials, the Package Registry rejects anonymous requests on the group-level endpoint.
|
||||
To work around this limitation, set up [authentication](#add-the-package-registry-as-a-source-for-nuget-packages).
|
||||
|
@ -352,12 +354,12 @@ the existing package is overwritten.
|
|||
|
||||
## Install packages
|
||||
|
||||
To install a NuGet package from the Package Registry, you must first
|
||||
[add a project-level or group-level endpoint](#add-the-package-registry-as-a-source-for-nuget-packages).
|
||||
|
||||
If multiple packages have the same name and version, when you install
|
||||
a package, the most recently-published package is retrieved.
|
||||
|
||||
To install a NuGet package from the Package Registry, you must first
|
||||
[add a project-level or group-level endpoint](#add-the-package-registry-as-a-source-for-nuget-packages).
|
||||
|
||||
### Install a package with the NuGet CLI
|
||||
|
||||
WARNING:
|
||||
|
|
|
@ -4,48 +4,55 @@ group: Configure
|
|||
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
|
||||
---
|
||||
|
||||
# GKE clusters (DEPRECATED) **(FREE)**
|
||||
|
||||
> - [Deprecated](https://gitlab.com/groups/gitlab-org/-/epics/6049) in GitLab 14.0.
|
||||
# Connect GKE clusters through cluster certificates **(FREE)**
|
||||
|
||||
WARNING:
|
||||
Use [Infrastructure as Code](../../infrastructure/index.md) to create new clusters. The method described in this document is deprecated as of GitLab 14.0.
|
||||
Use [Infrastrucure as Code](../../infrastructure/clusters/connect/new_gke_cluster.md)
|
||||
to create a cluster hosted on Google Kubernetes Engine (GKE).
|
||||
|
||||
Through GitLab, you can create new clusters and add existing clusters hosted on Amazon Elastic
|
||||
Kubernetes Service (EKS).
|
||||
Through GitLab, you can create new and connect existing clusters
|
||||
hosted on Google Kubernetes Engine (GKE).
|
||||
|
||||
GitLab supports adding new and existing GKE clusters.
|
||||
## Connect an existing GKE cluster
|
||||
|
||||
## GKE requirements
|
||||
If you already have a GKE cluster and want to connect it to GitLab,
|
||||
use the [GitLab Kubernetes Agent](../../clusters/agent/index.md).
|
||||
|
||||
Before creating your first cluster on Google GKE with GitLab integration, make sure the following
|
||||
requirements are met:
|
||||
Alternatively, you can [connect them with cluster certificates](add_existing_cluster.md),
|
||||
altough this method is not recommended for [security implications](../../infrastructure/clusters/connect/index.md#security-implications-for-clusters-connected-with-certificates).
|
||||
|
||||
- A [billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
|
||||
## Create a new GKE cluster from GitLab
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/25925) in GitLab 12.4, all the GKE clusters provisioned by GitLab are [VPC-native](https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips).
|
||||
|
||||
To create a new GKE cluster from GitLab, use [Infrastructure as Code](../../infrastructure/clusters/connect/new_gke_cluster.md).
|
||||
|
||||
Alternatively, you can [create new GKE clusters using cluster certificates](#create-a-new-cluster-on-gke-through-cluster-certificates-deprecated).
|
||||
Although still available in the GitLab UI, this method was deprecated
|
||||
in GitLab 14.0 and is scheduled for removal in GitLab 15.0.
|
||||
It also has [security implications](../../infrastructure/clusters/connect/index.md#security-implications-for-clusters-connected-with-certificates).
|
||||
|
||||
## Create a new cluster on GKE through cluster certificates (DEPRECATED)
|
||||
|
||||
> [Deprecated](https://gitlab.com/groups/gitlab-org/-/epics/6049) in GitLab 14.0.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- A [Google Cloud billing account](https://cloud.google.com/billing/docs/how-to/manage-billing-account)
|
||||
set up with access.
|
||||
- The Kubernetes Engine API and related service are enabled. It should work immediately but may
|
||||
- Kubernetes Engine API and related services enabled. It should work immediately but may
|
||||
take up to 10 minutes after you create a project. For more information see the
|
||||
["Before you begin" section of the Kubernetes Engine docs](https://cloud.google.com/kubernetes-engine/docs/quickstart#before-you-begin).
|
||||
|
||||
## Add an existing GKE cluster
|
||||
|
||||
If you already have a GKE cluster and want to integrate it with GitLab,
|
||||
see how to [add an existing cluster](add_existing_cluster.md).
|
||||
|
||||
## Create new GKE cluster
|
||||
|
||||
Starting from [GitLab 12.4](https://gitlab.com/gitlab-org/gitlab/-/issues/25925), all the GKE clusters
|
||||
provisioned by GitLab are [VPC-native](https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips).
|
||||
|
||||
Note the following:
|
||||
|
||||
- The [Google authentication integration](../../../integration/google.md) must be enabled in GitLab
|
||||
at the instance level. If that's not the case, ask your GitLab administrator to enable it. On
|
||||
GitLab.com, this is enabled.
|
||||
- Starting from [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55902), all GKE clusters
|
||||
- In [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/55902) and later, all GKE clusters
|
||||
created by GitLab are RBAC-enabled. Take a look at the [RBAC section](cluster_access.md#rbac-cluster-resources) for
|
||||
more information.
|
||||
- Starting from [GitLab 12.5](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18341), the
|
||||
- In [GitLab 12.5](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/18341) and later, the
|
||||
cluster's pod address IP range is set to `/16` instead of the regular `/14`. `/16` is a CIDR
|
||||
notation.
|
||||
- GitLab requires basic authentication enabled and a client certificate issued for the cluster to
|
||||
|
@ -54,9 +61,8 @@ Note the following:
|
|||
explicitly requests GKE to create clusters with basic authentication enabled and a client
|
||||
certificate.
|
||||
|
||||
### Creating the cluster on GKE
|
||||
|
||||
To create and add a new Kubernetes cluster to your project, group, or instance:
|
||||
To create new Kubernetes clusters to your project, group, or instance, through
|
||||
cluster certificates:
|
||||
|
||||
1. Navigate to your:
|
||||
- Project's **{cloud-gear}** **Infrastructure > Kubernetes clusters** page, for a project-level
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Source Code
|
||||
stage: Manage
|
||||
group: Workspace
|
||||
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"
|
||||
type: reference
|
||||
---
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Manage
|
||||
group: Workspace
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
@ -139,6 +139,11 @@ The following items are **not** exported:
|
|||
- Any encrypted tokens
|
||||
- Merge Request Approvers
|
||||
|
||||
These content rules also apply to creating projects from templates on the
|
||||
[group](../../group/custom_project_templates.md)
|
||||
or [instance](../../admin_area/custom_project_templates.md)
|
||||
levels, because the same export and import mechanisms are used.
|
||||
|
||||
NOTE:
|
||||
For more details on the specific data persisted in a project export, see the
|
||||
[`import_export.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/import_export/project/import_export.yml) file.
|
||||
|
@ -255,13 +260,13 @@ reduce the repository size for another import attempt.
|
|||
git reflog expire --expire=now --all
|
||||
git gc --prune=now --aggressive
|
||||
|
||||
# Prepare recreating an importable file
|
||||
# Prepare recreating an importable file
|
||||
git bundle create ../project.bundle smaller-tmp-main
|
||||
cd ..
|
||||
mv project/ ../"$EXPORT"-project
|
||||
cd ..
|
||||
|
||||
# Recreate an importable file
|
||||
# Recreate an importable file
|
||||
tar -czf "$EXPORT"-smaller.tar.gz --directory="$EXPORT"/ .
|
||||
```
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Source Code
|
||||
stage: Manage
|
||||
group: Workspace
|
||||
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"
|
||||
type: reference, index, howto
|
||||
---
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: Create
|
||||
group: Source Code
|
||||
stage: Manage
|
||||
group: Workspace
|
||||
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"
|
||||
---
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
stage: none
|
||||
group: unassigned
|
||||
stage: Manage
|
||||
group: Workspace
|
||||
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
|
||||
---
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def execute(shas)
|
||||
shas.each do |sha|
|
||||
shas.uniq.each do |sha|
|
||||
next unless sha.present? && commit_by(oid: sha)
|
||||
|
||||
next if kept_around?(sha)
|
||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
|||
retry_attempts = 0
|
||||
|
||||
begin
|
||||
ActiveRecord::Base.transaction do # rubocop: disable Database/MultipleDatabases
|
||||
subject.transaction do
|
||||
yield(subject)
|
||||
end
|
||||
rescue ActiveRecord::StaleObjectError
|
||||
|
|
|
@ -6569,17 +6569,20 @@ msgstr ""
|
|||
msgid "Checkout|%{name}'s GitLab subscription"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|%{quantity} GB of storage"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|%{quantity} storage pack"
|
||||
msgid_plural "Checkout|%{quantity} storage packs"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Checkout|%{selectedPlanText} plan"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|%{startDate} - %{endDate}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|%{totalCiMinutes} CI minute"
|
||||
msgid_plural "Checkout|%{totalCiMinutes} CI minutes"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Checkout|%{totalCiMinutes} CI minutes"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6655,6 +6658,9 @@ msgstr ""
|
|||
msgid "Checkout|Failed to register credit card. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|GB"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|GitLab group"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6691,6 +6697,9 @@ msgstr ""
|
|||
msgid "Checkout|State"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Storage packs"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Street address"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6712,6 +6721,9 @@ msgstr ""
|
|||
msgid "Checkout|Total minutes: %{quantity}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Total storage: %{quantity} GB"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|Users"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6730,7 +6742,10 @@ msgstr ""
|
|||
msgid "Checkout|company or team"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|x 1,000 minutes per pack = %{strong}"
|
||||
msgid "Checkout|minutes"
|
||||
msgstr ""
|
||||
|
||||
msgid "Checkout|x %{quantity} %{units} per pack ="
|
||||
msgstr ""
|
||||
|
||||
msgid "Cherry-pick this commit"
|
||||
|
@ -9234,6 +9249,9 @@ msgstr ""
|
|||
msgid "Copy link to chart"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy prefix"
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy reference"
|
||||
msgstr ""
|
||||
|
||||
|
@ -9456,6 +9474,9 @@ msgstr ""
|
|||
msgid "Create a Mattermost team for this group"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create a local proxy for storing frequently used upstream images. %{docLinkStart}Learn more%{docLinkEnd} about dependency proxies."
|
||||
msgstr ""
|
||||
|
||||
msgid "Create a local proxy for storing frequently used upstream images. %{link_start}Learn more%{link_end} about dependency proxies."
|
||||
msgstr ""
|
||||
|
||||
|
@ -25351,9 +25372,6 @@ msgstr ""
|
|||
msgid "Please set a new password before proceeding."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please share your feedback about %{featureName} %{linkStart}in this issue%{linkEnd} to help us improve the experience."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please solve the captcha"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -63,12 +63,12 @@
|
|||
"@rails/ujs": "6.1.3-2",
|
||||
"@sentry/browser": "5.30.0",
|
||||
"@sourcegraph/code-host-integration": "0.0.60",
|
||||
"@tiptap/core": "^2.0.0-beta.105",
|
||||
"@tiptap/core": "^2.0.0-beta.108",
|
||||
"@tiptap/extension-blockquote": "^2.0.0-beta.15",
|
||||
"@tiptap/extension-bold": "^2.0.0-beta.15",
|
||||
"@tiptap/extension-bullet-list": "^2.0.0-beta.15",
|
||||
"@tiptap/extension-code": "^2.0.0-beta.16",
|
||||
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.37",
|
||||
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.38",
|
||||
"@tiptap/extension-document": "^2.0.0-beta.13",
|
||||
"@tiptap/extension-dropcursor": "^2.0.0-beta.19",
|
||||
"@tiptap/extension-gapcursor": "^2.0.0-beta.19",
|
||||
|
@ -85,14 +85,14 @@
|
|||
"@tiptap/extension-strike": "^2.0.0-beta.17",
|
||||
"@tiptap/extension-subscript": "^2.0.0-beta.4",
|
||||
"@tiptap/extension-superscript": "^2.0.0-beta.4",
|
||||
"@tiptap/extension-table": "^2.0.0-beta.30",
|
||||
"@tiptap/extension-table": "^2.0.0-beta.31",
|
||||
"@tiptap/extension-table-cell": "^2.0.0-beta.15",
|
||||
"@tiptap/extension-table-header": "^2.0.0-beta.17",
|
||||
"@tiptap/extension-table-row": "^2.0.0-beta.14",
|
||||
"@tiptap/extension-task-item": "^2.0.0-beta.18",
|
||||
"@tiptap/extension-task-list": "^2.0.0-beta.17",
|
||||
"@tiptap/extension-text": "^2.0.0-beta.13",
|
||||
"@tiptap/vue-2": "^2.0.0-beta.50",
|
||||
"@tiptap/vue-2": "^2.0.0-beta.52",
|
||||
"@toast-ui/editor": "^2.5.2",
|
||||
"@toast-ui/vue-editor": "^2.5.2",
|
||||
"apollo-cache-inmemory": "^1.6.6",
|
||||
|
|
|
@ -38,7 +38,11 @@ module QA
|
|||
end
|
||||
end
|
||||
|
||||
context 'when using attachments in comments', :object_storage do
|
||||
context 'when using attachments in comments', :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
let(:png_file_name) { 'testfile.png' }
|
||||
let(:file_to_attach) do
|
||||
File.absolute_path(File.join('qa', 'fixtures', 'designs', png_file_name))
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'Composer Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'Generic Repository' do
|
||||
let(:project) do
|
||||
Resource::Project.fabricate_via_api! do |project|
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'Maven Repository with Gradle' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
include Runtime::Fixtures
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :reliable, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :reliable, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'Maven Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :reliable, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :reliable, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'npm registry' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
include Runtime::Fixtures
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'NuGet Repository' do
|
||||
using RSpec::Parameterized::TableSyntax
|
||||
include Runtime::Fixtures
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :packages, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'PyPI Repository' do
|
||||
include Runtime::Fixtures
|
||||
let(:project) do
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module QA
|
||||
RSpec.describe 'Package', :orchestrated, :requires_admin, :packages, :object_storage do
|
||||
RSpec.describe 'Package', :orchestrated, :requires_admin, :packages, :object_storage, quarantine: {
|
||||
only: { job: 'object_storage' },
|
||||
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/341209#note_681513082',
|
||||
type: :investigating
|
||||
} do
|
||||
describe 'RubyGems Repository' do
|
||||
include Runtime::Fixtures
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ FactoryBot.define do
|
|||
transient { ci_ref_presence { true } }
|
||||
|
||||
before(:create) do |pipeline, evaluator|
|
||||
pipeline.ensure_project_iid!
|
||||
pipeline.ensure_ci_ref! if evaluator.ci_ref_presence && pipeline.ci_ref_id.nil?
|
||||
end
|
||||
|
||||
|
|
|
@ -133,8 +133,9 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do
|
|||
expect(doc.at_css('td:contains("Baz")')['align']).to eq 'left'
|
||||
end
|
||||
|
||||
# note that 2 are from the hardcoded <sup>, and 2 from footnotes
|
||||
aggregate_failures 'permits superscript elements' do
|
||||
expect(doc).to have_selector('sup', count: 2)
|
||||
expect(doc).to have_selector('sup', count: 4)
|
||||
end
|
||||
|
||||
aggregate_failures 'permits subscript elements' do
|
||||
|
@ -148,6 +149,11 @@ RSpec.describe 'GitLab Markdown', :aggregate_failures do
|
|||
aggregate_failures "removes `href` from `a` elements if it's fishy" do
|
||||
expect(doc).not_to have_selector('a[href*="javascript"]')
|
||||
end
|
||||
|
||||
aggregate_failures 'permits footnotes' do
|
||||
expect(doc).to have_selector('section.footnotes ol li p:contains("Footnote 1")')
|
||||
expect(doc).to have_selector('section.footnotes ol li p:contains("Footnote with w")')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Escaping' do
|
||||
|
|
|
@ -52,6 +52,15 @@ Redcarpet supports this superscript syntax ( x^2 ).
|
|||
|
||||
This (C<sub>6</sub>H<sub>12</sub>O<sub>6</sub>) is an example of subscripts in Markdown.
|
||||
|
||||
### Footnotes
|
||||
|
||||
This is footnote 1.[^f1]
|
||||
|
||||
A footnote with a `w` was failing.[^f2-w]
|
||||
|
||||
[^f1]: Footnote 1
|
||||
[^f2-w]: Footnote with w
|
||||
|
||||
### Next step
|
||||
|
||||
After the Markdown has been turned into HTML, it gets passed through...
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
import { GlAlert, GlFormInputGroup, GlFormGroup, GlSprintf } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
|
||||
import DependencyProxyApp from '~/packages_and_registries/dependency_proxy/app.vue';
|
||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||
|
||||
describe('DependencyProxyApp', () => {
|
||||
let wrapper;
|
||||
|
||||
const provideDefaults = {
|
||||
groupPath: 'gitlab-org',
|
||||
dependencyProxyAvailable: true,
|
||||
};
|
||||
|
||||
function createComponent({ provide = provideDefaults } = {}) {
|
||||
wrapper = shallowMountExtended(DependencyProxyApp, {
|
||||
provide,
|
||||
stubs: {
|
||||
GlFormInputGroup,
|
||||
GlFormGroup,
|
||||
GlSprintf,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const findProxyNotAvailableAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findClipBoardButton = () => wrapper.findComponent(ClipboardButton);
|
||||
const findFormGroup = () => wrapper.findComponent(GlFormGroup);
|
||||
const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
|
||||
const findMainArea = () => wrapper.findByTestId('main-area');
|
||||
const findProxyCountText = () => wrapper.findByTestId('proxy-count');
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
||||
describe('when the dependency proxy is not available', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ provide: { ...provideDefaults, dependencyProxyAvailable: false } });
|
||||
});
|
||||
|
||||
it('renders an info alert', () => {
|
||||
expect(findProxyNotAvailableAlert().text()).toBe(
|
||||
DependencyProxyApp.i18n.proxyNotAvailableText,
|
||||
);
|
||||
});
|
||||
|
||||
it('does not render the main area', () => {
|
||||
expect(findMainArea().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when the dependency proxy is available', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('does not render the info alert', () => {
|
||||
expect(findProxyNotAvailableAlert().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders the main area', () => {
|
||||
expect(findMainArea().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders a form group with a label', () => {
|
||||
expect(findFormGroup().attributes('label')).toBe(DependencyProxyApp.i18n.proxyImagePrefix);
|
||||
});
|
||||
|
||||
it('renders a form input group', () => {
|
||||
expect(findFormInputGroup().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('form input group has a clipboard button', () => {
|
||||
expect(findClipBoardButton().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('from group has a description with proxy count', () => {
|
||||
expect(findProxyCountText().text()).toBe('Contains 0 blobs of images (0 bytes)');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,4 +1,10 @@
|
|||
import { GlButton, GlDropdown, GlDropdownItem, GlDropdownSectionHeader } from '@gitlab/ui';
|
||||
import {
|
||||
GlButton,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownSectionHeader,
|
||||
GlSearchBoxByType,
|
||||
} from '@gitlab/ui';
|
||||
import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
|
@ -34,9 +40,6 @@ describe('NewProjectUrlSelect component', () => {
|
|||
const localVue = createLocalVue();
|
||||
localVue.use(VueApollo);
|
||||
|
||||
const requestHandlers = [[searchQuery, jest.fn().mockResolvedValue({ data })]];
|
||||
const apolloProvider = createMockApollo(requestHandlers);
|
||||
|
||||
const provide = {
|
||||
namespaceFullPath: 'h5bp',
|
||||
namespaceId: '28',
|
||||
|
@ -44,11 +47,25 @@ describe('NewProjectUrlSelect component', () => {
|
|||
trackLabel: 'blank_project',
|
||||
};
|
||||
|
||||
const mountComponent = ({ mountFn = shallowMount } = {}) =>
|
||||
mountFn(NewProjectUrlSelect, { localVue, apolloProvider, provide });
|
||||
const mountComponent = ({ search = '', queryResponse = data, mountFn = shallowMount } = {}) => {
|
||||
const requestHandlers = [[searchQuery, jest.fn().mockResolvedValue({ data: queryResponse })]];
|
||||
const apolloProvider = createMockApollo(requestHandlers);
|
||||
|
||||
return mountFn(NewProjectUrlSelect, {
|
||||
localVue,
|
||||
apolloProvider,
|
||||
provide,
|
||||
data() {
|
||||
return {
|
||||
search,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findButtonLabel = () => wrapper.findComponent(GlButton);
|
||||
const findDropdown = () => wrapper.findComponent(GlDropdown);
|
||||
const findInput = () => wrapper.findComponent(GlSearchBoxByType);
|
||||
const findHiddenInput = () => wrapper.find('input');
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -74,6 +91,19 @@ describe('NewProjectUrlSelect component', () => {
|
|||
expect(findHiddenInput().attributes('value')).toBe(provide.namespaceId);
|
||||
});
|
||||
|
||||
it('focuses on the input when the dropdown is opened', async () => {
|
||||
wrapper = mountComponent({ mountFn: mount });
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
const spy = jest.spyOn(findInput().vm, 'focusInput');
|
||||
|
||||
findDropdown().vm.$emit('shown');
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('renders expected dropdown items', async () => {
|
||||
wrapper = mountComponent({ mountFn: mount });
|
||||
|
||||
|
@ -89,6 +119,27 @@ describe('NewProjectUrlSelect component', () => {
|
|||
expect(listItems.at(4).text()).toBe(data.currentUser.namespace.fullPath);
|
||||
});
|
||||
|
||||
it('renders `No matches found` when there are no matching dropdown items', async () => {
|
||||
const queryResponse = {
|
||||
currentUser: {
|
||||
groups: {
|
||||
nodes: [],
|
||||
},
|
||||
namespace: {
|
||||
id: 'gid://gitlab/Namespace/1',
|
||||
fullPath: 'root',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
wrapper = mountComponent({ search: 'no matches', queryResponse, mountFn: mount });
|
||||
|
||||
jest.runOnlyPendingTimers();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(wrapper.find('li').text()).toBe('No matches found');
|
||||
});
|
||||
|
||||
it('updates hidden input with selected namespace', async () => {
|
||||
wrapper = mountComponent();
|
||||
|
||||
|
|
|
@ -0,0 +1,204 @@
|
|||
import {
|
||||
GlSprintf,
|
||||
GlDropdown,
|
||||
GlDropdownItem,
|
||||
GlDropdownSectionHeader,
|
||||
GlSearchBoxByType,
|
||||
} from '@gitlab/ui';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import { nextTick } from 'vue';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { getUsers, getGroups, getDeployKeys } from '~/projects/settings/api/access_dropdown_api';
|
||||
import AccessDropdown, { i18n } from '~/projects/settings/components/access_dropdown.vue';
|
||||
import { ACCESS_LEVELS } from '~/projects/settings/constants';
|
||||
|
||||
jest.mock('~/projects/settings/api/access_dropdown_api', () => ({
|
||||
getUsers: jest.fn().mockResolvedValue({ data: [{ id: 1 }, { id: 2 }] }),
|
||||
getGroups: jest.fn().mockResolvedValue({ data: [{ id: 3 }, { id: 4 }, { id: 5 }] }),
|
||||
getDeployKeys: jest.fn().mockResolvedValue({
|
||||
data: [
|
||||
{ id: 6, title: 'key1', fingerprint: 'abcdefghijklmnop', owner: { name: 'user1' } },
|
||||
{ id: 7, title: 'key1', fingerprint: 'abcdefghijklmnop', owner: { name: 'user2' } },
|
||||
{ id: 8, title: 'key1', fingerprint: 'abcdefghijklmnop', owner: { name: 'user3' } },
|
||||
{ id: 9, title: 'key1', fingerprint: 'abcdefghijklmnop', owner: { name: 'user4' } },
|
||||
],
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('Access Level Dropdown', () => {
|
||||
let wrapper;
|
||||
const mockAccessLevelsData = [
|
||||
{
|
||||
id: 42,
|
||||
text: 'Dummy Role',
|
||||
},
|
||||
];
|
||||
|
||||
const createComponent = ({
|
||||
accessLevelsData = mockAccessLevelsData,
|
||||
accessLevel = ACCESS_LEVELS.PUSH,
|
||||
hasLicense = true,
|
||||
} = {}) => {
|
||||
wrapper = shallowMount(AccessDropdown, {
|
||||
propsData: {
|
||||
accessLevelsData,
|
||||
accessLevel,
|
||||
hasLicense,
|
||||
},
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
GlDropdown,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const findDropdown = () => wrapper.findComponent(GlDropdown);
|
||||
const findDropdownToggleLabel = () => findDropdown().props('text');
|
||||
const findAllDropdownItems = () => findDropdown().findAllComponents(GlDropdownItem);
|
||||
const findAllDropdownHeaders = () => findDropdown().findAllComponents(GlDropdownSectionHeader);
|
||||
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
|
||||
|
||||
describe('data request', () => {
|
||||
it('should make an api call for users, groups && deployKeys when user has a license', () => {
|
||||
createComponent();
|
||||
expect(getUsers).toHaveBeenCalled();
|
||||
expect(getGroups).toHaveBeenCalled();
|
||||
expect(getDeployKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should make an api call for deployKeys but not for users or groups when user does not have a license', () => {
|
||||
createComponent({ hasLicense: false });
|
||||
expect(getUsers).not.toHaveBeenCalled();
|
||||
expect(getGroups).not.toHaveBeenCalled();
|
||||
expect(getDeployKeys).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should make api calls when search query is updated', async () => {
|
||||
createComponent();
|
||||
const query = 'root';
|
||||
|
||||
findSearchBox().vm.$emit('input', query);
|
||||
await nextTick();
|
||||
expect(getUsers).toHaveBeenCalledWith(query);
|
||||
expect(getGroups).toHaveBeenCalled();
|
||||
expect(getDeployKeys).toHaveBeenCalledWith(query);
|
||||
});
|
||||
});
|
||||
describe('layout', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('renders headers for each section ', () => {
|
||||
expect(findAllDropdownHeaders()).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('renders dropdown item for each access level type', () => {
|
||||
expect(findAllDropdownItems()).toHaveLength(10);
|
||||
});
|
||||
});
|
||||
describe('toggleLabel', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
const triggerNthItemClick = async (n) => {
|
||||
findAllDropdownItems().at(n).trigger('click');
|
||||
await nextTick();
|
||||
};
|
||||
|
||||
it('when no items selected displays a default label and has default CSS class ', () => {
|
||||
expect(findDropdownToggleLabel()).toBe(i18n.selectUsers);
|
||||
expect(findDropdown().props('toggleClass')).toBe('gl-text-gray-500!');
|
||||
});
|
||||
|
||||
it('displays a number of selected items for each group level', async () => {
|
||||
findAllDropdownItems().wrappers.forEach((item) => {
|
||||
item.trigger('click');
|
||||
});
|
||||
await nextTick();
|
||||
expect(findDropdownToggleLabel()).toBe('1 role, 2 users, 4 deploy keys, 3 groups');
|
||||
});
|
||||
|
||||
it('with only role selected displays the role name and has no class applied', async () => {
|
||||
await triggerNthItemClick(0);
|
||||
expect(findDropdownToggleLabel()).toBe('Dummy Role');
|
||||
expect(findDropdown().props('toggleClass')).toBe('');
|
||||
});
|
||||
|
||||
it('with only groups selected displays the number of selected groups', async () => {
|
||||
await triggerNthItemClick(1);
|
||||
await triggerNthItemClick(2);
|
||||
await triggerNthItemClick(3);
|
||||
expect(findDropdownToggleLabel()).toBe('3 groups');
|
||||
expect(findDropdown().props('toggleClass')).toBe('');
|
||||
});
|
||||
|
||||
it('with only users selected displays the number of selected users', async () => {
|
||||
await triggerNthItemClick(4);
|
||||
await triggerNthItemClick(5);
|
||||
expect(findDropdownToggleLabel()).toBe('2 users');
|
||||
expect(findDropdown().props('toggleClass')).toBe('');
|
||||
});
|
||||
|
||||
it('with users and groups selected displays the number of selected users & groups', async () => {
|
||||
await triggerNthItemClick(1);
|
||||
await triggerNthItemClick(2);
|
||||
await triggerNthItemClick(4);
|
||||
await triggerNthItemClick(5);
|
||||
expect(findDropdownToggleLabel()).toBe('2 users, 2 groups');
|
||||
expect(findDropdown().props('toggleClass')).toBe('');
|
||||
});
|
||||
|
||||
it('with users and deploy keys selected displays the number of selected users & keys', async () => {
|
||||
await triggerNthItemClick(1);
|
||||
await triggerNthItemClick(2);
|
||||
await triggerNthItemClick(6);
|
||||
expect(findDropdownToggleLabel()).toBe('1 deploy key, 2 groups');
|
||||
expect(findDropdown().props('toggleClass')).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('selecting an item', () => {
|
||||
beforeEach(async () => {
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
});
|
||||
|
||||
it('selects the item on click and deselects on the next click ', async () => {
|
||||
const item = findAllDropdownItems().at(1);
|
||||
item.trigger('click');
|
||||
await nextTick();
|
||||
expect(item.props('isChecked')).toBe(true);
|
||||
item.trigger('click');
|
||||
await nextTick();
|
||||
expect(item.props('isChecked')).toBe(false);
|
||||
});
|
||||
|
||||
it('emits an update on selection ', async () => {
|
||||
const spy = jest.spyOn(wrapper.vm, '$emit');
|
||||
findAllDropdownItems().at(4).trigger('click');
|
||||
findAllDropdownItems().at(3).trigger('click');
|
||||
await nextTick();
|
||||
expect(spy).toHaveBeenLastCalledWith('select', [
|
||||
{ id: 5, type: 'group' },
|
||||
{ id: 1, type: 'user' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on dropdown open', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('should set the search input focus', () => {
|
||||
wrapper.vm.$refs.search.focusInput = jest.fn();
|
||||
findDropdown().vm.$emit('shown');
|
||||
|
||||
expect(wrapper.vm.$refs.search.focusInput).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
|
||||
import { GlAlert, GlSprintf } from '@gitlab/ui';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
import Component from '~/vue_shared/components/dismissible_feedback_alert.vue';
|
||||
|
@ -8,20 +8,13 @@ describe('Dismissible Feedback Alert', () => {
|
|||
|
||||
let wrapper;
|
||||
|
||||
const defaultProps = {
|
||||
featureName: 'Dependency List',
|
||||
feedbackLink: 'https://gitlab.link',
|
||||
};
|
||||
|
||||
const featureName = 'Dependency List';
|
||||
const STORAGE_DISMISSAL_KEY = 'dependency_list_feedback_dismissed';
|
||||
|
||||
const createComponent = ({ props, shallow } = {}) => {
|
||||
const mountFn = shallow ? shallowMount : mount;
|
||||
|
||||
const createComponent = ({ mountFn = shallowMount } = {}) => {
|
||||
wrapper = mountFn(Component, {
|
||||
propsData: {
|
||||
...defaultProps,
|
||||
...props,
|
||||
featureName,
|
||||
},
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
|
@ -34,8 +27,8 @@ describe('Dismissible Feedback Alert', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
const findAlert = () => wrapper.find(GlAlert);
|
||||
const findLink = () => wrapper.find(GlLink);
|
||||
const createFullComponent = () => createComponent({ mountFn: mount });
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
|
||||
describe('with default', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -46,17 +39,6 @@ describe('Dismissible Feedback Alert', () => {
|
|||
expect(findAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('contains feature name', () => {
|
||||
expect(findAlert().text()).toContain(defaultProps.featureName);
|
||||
});
|
||||
|
||||
it('contains provided link', () => {
|
||||
const link = findLink();
|
||||
|
||||
expect(link.attributes('href')).toBe(defaultProps.feedbackLink);
|
||||
expect(link.attributes('target')).toBe('_blank');
|
||||
});
|
||||
|
||||
it('should have the storage key set', () => {
|
||||
expect(wrapper.vm.storageKey).toBe(STORAGE_DISMISSAL_KEY);
|
||||
});
|
||||
|
@ -65,7 +47,7 @@ describe('Dismissible Feedback Alert', () => {
|
|||
describe('dismissible', () => {
|
||||
describe('after dismissal', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ shallow: false });
|
||||
createFullComponent();
|
||||
findAlert().vm.$emit('dismiss');
|
||||
});
|
||||
|
||||
|
@ -81,7 +63,7 @@ describe('Dismissible Feedback Alert', () => {
|
|||
describe('already dismissed', () => {
|
||||
it('should not show the alert once dismissed', async () => {
|
||||
localStorage.setItem(STORAGE_DISMISSAL_KEY, 'true');
|
||||
createComponent({ shallow: false });
|
||||
createFullComponent();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
expect(findAlert().exists()).toBe(false);
|
||||
|
|
|
@ -34,15 +34,16 @@ RSpec.describe Banzai::Pipeline::FullPipeline do
|
|||
let(:identifier) { html[/.*fnref1-(\d+).*/, 1] }
|
||||
let(:footnote_markdown) do
|
||||
<<~EOF
|
||||
first[^1] and second[^second]
|
||||
first[^1] and second[^second] and twenty[^twenty]
|
||||
[^1]: one
|
||||
[^second]: two
|
||||
[^twenty]: twenty
|
||||
EOF
|
||||
end
|
||||
|
||||
let(:filtered_footnote) do
|
||||
<<~EOF
|
||||
<p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup></p>
|
||||
<p dir="auto">first<sup class="footnote-ref"><a href="#fn1-#{identifier}" id="fnref1-#{identifier}">1</a></sup> and second<sup class="footnote-ref"><a href="#fn2-#{identifier}" id="fnref2-#{identifier}">2</a></sup> and twenty<sup class="footnote-ref"><a href="#fn3-#{identifier}" id="fnref3-#{identifier}">3</a></sup></p>
|
||||
|
||||
<section class="footnotes"><ol>
|
||||
<li id="fn1-#{identifier}">
|
||||
|
@ -51,6 +52,9 @@ RSpec.describe Banzai::Pipeline::FullPipeline do
|
|||
<li id="fn2-#{identifier}">
|
||||
<p>two <a href="#fnref2-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
|
||||
</li>
|
||||
<li id="fn3-#{identifier}">
|
||||
<p>twenty <a href="#fnref3-#{identifier}" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
|
||||
</li>
|
||||
</ol></section>
|
||||
EOF
|
||||
end
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require_migration!('cleanup_bigint_conversion_for_ci_builds')
|
||||
|
||||
RSpec.describe CleanupBigintConversionForCiBuilds do
|
||||
let(:ci_builds) { table(:ci_builds) }
|
||||
|
||||
it 'correctly migrates up and down' do
|
||||
reversible_migration do |migration|
|
||||
migration.before -> {
|
||||
expect(ci_builds.column_names).to include('id_convert_to_bigint')
|
||||
expect(ci_builds.column_names).to include('stage_id_convert_to_bigint')
|
||||
}
|
||||
|
||||
migration.after -> {
|
||||
ci_builds.reset_column_information
|
||||
expect(ci_builds.column_names).not_to include('id_convert_to_bigint')
|
||||
expect(ci_builds.column_names).not_to include('stage_id_convert_to_bigint')
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5289,4 +5289,10 @@ RSpec.describe Ci::Build do
|
|||
expect(build.reload.queuing_entry).not_to be_present
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not generate cross DB queries when a record is created via FactoryBot' do
|
||||
with_cross_database_modification_prevented do
|
||||
create(:ci_build)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,7 +41,7 @@ module MarkdownMatchers
|
|||
set_default_markdown_messages
|
||||
|
||||
match do |actual|
|
||||
expect(actual).to have_selector('gl-emoji', count: 10)
|
||||
expect(actual).to have_selector('gl-emoji', count: 12)
|
||||
|
||||
emoji_element = actual.at_css('gl-emoji')
|
||||
expect(emoji_element['data-name'].to_s).not_to be_empty
|
||||
|
|
|
@ -68,5 +68,20 @@ RSpec.describe RunPipelineScheduleWorker do
|
|||
worker.perform(pipeline_schedule.id, user.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline cannot be created' do
|
||||
before do
|
||||
allow(Ci::CreatePipelineService).to receive(:new) { raise Ci::CreatePipelineService::CreateError }
|
||||
end
|
||||
|
||||
it 'logging a pipeline error' do
|
||||
expect(worker)
|
||||
.to receive(:log_extra_metadata_on_done)
|
||||
.with(:pipeline_creation_error, an_instance_of(Ci::CreatePipelineService::CreateError))
|
||||
.and_call_original
|
||||
|
||||
worker.perform(pipeline_schedule.id, user.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
82
yarn.lock
82
yarn.lock
|
@ -1467,10 +1467,10 @@
|
|||
dom-accessibility-api "^0.5.1"
|
||||
pretty-format "^26.4.2"
|
||||
|
||||
"@tiptap/core@^2.0.0-beta.105":
|
||||
version "2.0.0-beta.105"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.105.tgz#3d758cbbf3e8c9b806d1017cd2e7444f192d8109"
|
||||
integrity sha512-ut0ts9hrJXUJlSXZIN8iEt2TPbqYLFBanucUAr0ENLjIlpyyNVXz9IhS3bBEmo7vWExirZmY8O9oidQztesf2A==
|
||||
"@tiptap/core@^2.0.0-beta.108":
|
||||
version "2.0.0-beta.108"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/core/-/core-2.0.0-beta.108.tgz#fdab0b549c6915d2e1710ecc915d219857c21eef"
|
||||
integrity sha512-cUMAkiCHVQk7EYyge+ChFDLBl9SktVOrFogHnjUJxQw+r1iesXf8A6u8bqi/TCMoWQetyP7ELpscMpxNaIE/rg==
|
||||
dependencies:
|
||||
"@types/prosemirror-commands" "^1.0.4"
|
||||
"@types/prosemirror-inputrules" "^1.0.4"
|
||||
|
@ -1479,7 +1479,7 @@
|
|||
"@types/prosemirror-schema-list" "^1.0.3"
|
||||
"@types/prosemirror-state" "^1.2.7"
|
||||
"@types/prosemirror-transform" "^1.1.4"
|
||||
"@types/prosemirror-view" "^1.19.0"
|
||||
"@types/prosemirror-view" "^1.19.1"
|
||||
prosemirror-commands "^1.1.10"
|
||||
prosemirror-inputrules "^1.1.3"
|
||||
prosemirror-keymap "^1.1.3"
|
||||
|
@ -1487,7 +1487,7 @@
|
|||
prosemirror-schema-list "^1.1.5"
|
||||
prosemirror-state "^1.3.4"
|
||||
prosemirror-transform "^1.3.2"
|
||||
prosemirror-view "^1.20.0"
|
||||
prosemirror-view "^1.20.1"
|
||||
|
||||
"@tiptap/extension-blockquote@^2.0.0-beta.15":
|
||||
version "2.0.0-beta.15"
|
||||
|
@ -1501,13 +1501,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@tiptap/extension-bold/-/extension-bold-2.0.0-beta.15.tgz#cf9ddb3fc316be9707753ad4e497bfb8a3ebb0c2"
|
||||
integrity sha512-jKyV6iiwhxwa0+7uuKD74jNDVNLNOS1GmU14MgaA95pY5e1fyaRBPPX8Gtt89niz2CLOY711AV17RPZTe/e60w==
|
||||
|
||||
"@tiptap/extension-bubble-menu@^2.0.0-beta.33":
|
||||
version "2.0.0-beta.33"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.33.tgz#50ee84c25118f7ee932385961211b64496355c74"
|
||||
integrity sha512-EvXSXyeLFnOAEPvbVz9B4ppP+paDGyK/Fy9b36fkH0hacFLS/lKb/8HrfTXItc0uZIe6xY6RhXTdkx1eAPTdTA==
|
||||
"@tiptap/extension-bubble-menu@^2.0.0-beta.34":
|
||||
version "2.0.0-beta.34"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-bubble-menu/-/extension-bubble-menu-2.0.0-beta.34.tgz#07598730c3ac755c86b2ac1e283b5be3591fbb0a"
|
||||
integrity sha512-vw2RpwgqJUE7tpDayOey1AYs2qp1J0TVdlo4HYzlOWV1kJ+2Zevf0oA83j+IX6d0XmYqNTp+r+VgLZEd0K1Neg==
|
||||
dependencies:
|
||||
prosemirror-state "^1.3.4"
|
||||
prosemirror-view "^1.20.0"
|
||||
prosemirror-view "^1.20.1"
|
||||
tippy.js "^6.3.1"
|
||||
|
||||
"@tiptap/extension-bullet-list@^2.0.0-beta.15":
|
||||
|
@ -1517,17 +1517,17 @@
|
|||
dependencies:
|
||||
prosemirror-inputrules "^1.1.3"
|
||||
|
||||
"@tiptap/extension-code-block-lowlight@2.0.0-beta.37":
|
||||
version "2.0.0-beta.37"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.37.tgz#672b2f21d49077285a507c2e204b07085df93906"
|
||||
integrity sha512-EN8MoCZKB23nHNqgvB/wOS84ySUY9ahB6oao7wDpKAYqBkAF/hEXmDsylDySvyCKXf824lS/vztfaF0hHQbQ/g==
|
||||
"@tiptap/extension-code-block-lowlight@2.0.0-beta.38":
|
||||
version "2.0.0-beta.38"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-code-block-lowlight/-/extension-code-block-lowlight-2.0.0-beta.38.tgz#2d0dc97ce2a25f4a8bd57fa179dbe591f3d66440"
|
||||
integrity sha512-1JVmesa0RuKMYUKLbW34hCqHjPm5gRKv9NOU6jhI90xJHWG0pe+Am6MEfWosilB66DtcE43zP08aiKWgWBvI2A==
|
||||
dependencies:
|
||||
"@tiptap/extension-code-block" "^2.0.0-beta.18"
|
||||
"@types/lowlight" "^0.0.3"
|
||||
lowlight "^1.20.0"
|
||||
prosemirror-model "^1.14.3"
|
||||
prosemirror-state "^1.3.4"
|
||||
prosemirror-view "^1.20.0"
|
||||
prosemirror-view "^1.20.1"
|
||||
|
||||
"@tiptap/extension-code-block@^2.0.0-beta.18":
|
||||
version "2.0.0-beta.18"
|
||||
|
@ -1554,13 +1554,13 @@
|
|||
"@types/prosemirror-dropcursor" "^1.0.3"
|
||||
prosemirror-dropcursor "^1.3.5"
|
||||
|
||||
"@tiptap/extension-floating-menu@^2.0.0-beta.27":
|
||||
version "2.0.0-beta.27"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.27.tgz#692686854116823be624028fd8d73aa900cf71a9"
|
||||
integrity sha512-PgoqO5OxBDMFZroXFD7mpwfDLxeG44xFHu6WK4Gf3O8jqGAQpEr4rMKyTnoNL9PYFhEwtNBzxUopumOT0Po/zQ==
|
||||
"@tiptap/extension-floating-menu@^2.0.0-beta.28":
|
||||
version "2.0.0-beta.28"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-floating-menu/-/extension-floating-menu-2.0.0-beta.28.tgz#be45bd0a558498e0fdfc593832726618e69ee8fc"
|
||||
integrity sha512-GxrD4i75Px6RLlY6ZZRU5C5aCjnEiKODA07TpyITlrB6cwmdzZG4L7n+x4Z/VGiUu6+fkiLpnjLbX/Fbk9Yn2w==
|
||||
dependencies:
|
||||
prosemirror-state "^1.3.4"
|
||||
prosemirror-view "^1.20.0"
|
||||
prosemirror-view "^1.20.1"
|
||||
tippy.js "^6.3.1"
|
||||
|
||||
"@tiptap/extension-gapcursor@^2.0.0-beta.19":
|
||||
|
@ -1662,13 +1662,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@tiptap/extension-table-row/-/extension-table-row-2.0.0-beta.14.tgz#9ec98c73e309ee966b71ccd140019874d179e0c8"
|
||||
integrity sha512-mewdlTqgBCyzeZIZ6F08gfuzwsiYjQ7BvABo2UhDfr0+EN2UvfJj0bT3tGgeZhMxT5Js2DXL+c+ZOVJxWJ9faQ==
|
||||
|
||||
"@tiptap/extension-table@^2.0.0-beta.30":
|
||||
version "2.0.0-beta.30"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.30.tgz#f6ff97ea71b17ecf3371ddf80374df9f49042334"
|
||||
integrity sha512-s6+HRo3sFv7SUprsUAAF27hg7inITpzl78If3XdrpscuzTVuRmd7GsFnY+aZGPVikekwCMjp/0klE92P4A7w0w==
|
||||
"@tiptap/extension-table@^2.0.0-beta.31":
|
||||
version "2.0.0-beta.31"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/extension-table/-/extension-table-2.0.0-beta.31.tgz#96987fe14017be2fd3e4dbd2ce349eec641724de"
|
||||
integrity sha512-yMqnbxaq2DjaZ6EOE9FLSQSO+qHH7oE0rA+ahQkJdy9KycSboKthXBY7P9JeXxariTyD2B/My9x41cuDLWes9w==
|
||||
dependencies:
|
||||
prosemirror-tables "^1.1.1"
|
||||
prosemirror-view "^1.20.0"
|
||||
prosemirror-view "^1.20.1"
|
||||
|
||||
"@tiptap/extension-task-item@^2.0.0-beta.18":
|
||||
version "2.0.0-beta.18"
|
||||
|
@ -1687,14 +1687,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@tiptap/extension-text/-/extension-text-2.0.0-beta.13.tgz#da0af8d9a3f149d20076e15d88c6af21fb6d940f"
|
||||
integrity sha512-0EtAwuRldCAoFaL/iXgkRepEeOd55rPg5N4FQUN1xTwZT7PDofukP0DG/2jff/Uj17x4uTaJAa9qlFWuNnDvjw==
|
||||
|
||||
"@tiptap/vue-2@^2.0.0-beta.50":
|
||||
version "2.0.0-beta.50"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.50.tgz#1c1c3e7d696aaa167fc776e11b393b182f1bfbb4"
|
||||
integrity sha512-o325eQbSoxxd6QXc27Mjd+T+yj1CnSTo0IQkbjbzYure8E0ReYXzW2wx5oE6DZA43Ejss2KPXZDEgiUNYRyX1Q==
|
||||
"@tiptap/vue-2@^2.0.0-beta.52":
|
||||
version "2.0.0-beta.52"
|
||||
resolved "https://registry.yarnpkg.com/@tiptap/vue-2/-/vue-2-2.0.0-beta.52.tgz#b61fd95f2368a86e64bee20b35a85a69a48fc930"
|
||||
integrity sha512-81UfvJTK68hy+KZDdHSbtMVXbUhkZ8zW9u+dotga34YHgfTeZjO3UrF5eJ34dwzsPaaSU+buFu/TMH5vP3KcWQ==
|
||||
dependencies:
|
||||
"@tiptap/extension-bubble-menu" "^2.0.0-beta.33"
|
||||
"@tiptap/extension-floating-menu" "^2.0.0-beta.27"
|
||||
prosemirror-view "^1.20.0"
|
||||
"@tiptap/extension-bubble-menu" "^2.0.0-beta.34"
|
||||
"@tiptap/extension-floating-menu" "^2.0.0-beta.28"
|
||||
prosemirror-view "^1.20.1"
|
||||
|
||||
"@toast-ui/editor@^2.5.2":
|
||||
version "2.5.2"
|
||||
|
@ -1950,10 +1950,10 @@
|
|||
dependencies:
|
||||
"@types/prosemirror-model" "*"
|
||||
|
||||
"@types/prosemirror-view@*", "@types/prosemirror-view@^1.19.0":
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/prosemirror-view/-/prosemirror-view-1.19.0.tgz#35320b6875ae7c750bce799cccf735e5da91af7a"
|
||||
integrity sha512-Y8OX9L+Yni0HgXAN9wcNSf61IId13uqpURnRC5WkmCOlVDsr35vfGjj+tcaQL4dZzblsu3bRkXI/c0oGXp+xgw==
|
||||
"@types/prosemirror-view@*", "@types/prosemirror-view@^1.19.1":
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/prosemirror-view/-/prosemirror-view-1.19.1.tgz#f12309ef07dfb701d20c2e4d0292d42ba34a081b"
|
||||
integrity sha512-fyQ4NVxAdfISWrE2qT8cpZdosXoH/1JuVYMBs9CdaXPbvi/8R2L2tkkcMRM314piKrO8nfYH5OBZKzP2Ax3jtA==
|
||||
dependencies:
|
||||
"@types/prosemirror-model" "*"
|
||||
"@types/prosemirror-state" "*"
|
||||
|
@ -9670,10 +9670,10 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor
|
|||
dependencies:
|
||||
prosemirror-model "^1.0.0"
|
||||
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.20.0:
|
||||
version "1.20.0"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.20.0.tgz#64198845f0d112c14a5594732c46a96ac3d9d828"
|
||||
integrity sha512-OqU/bHUIiJhpyb2ytX4fLalYAJJOyZ0k5H0AibP/WPsdHq9CqmJFU676gO+N8WWhR5tVz1NxsqMZgEBy5Lc6GQ==
|
||||
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.13.3, prosemirror-view@^1.16.5, prosemirror-view@^1.20.1:
|
||||
version "1.20.1"
|
||||
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.20.1.tgz#174ba8ca358c73cc05e9a92a3d252bcf181ea337"
|
||||
integrity sha512-djWORhy3a706mUH4A2dgEEV0IPZqQd1tFyz/ZVHJNoqhSgq82FwG6dq7uqHeUB2KdVSNfI2yc3rwfqlC/ll2pA==
|
||||
dependencies:
|
||||
prosemirror-model "^1.14.3"
|
||||
prosemirror-state "^1.0.0"
|
||||
|
|
Loading…
Reference in New Issue