Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
4cd1329b80
commit
47a3dc6551
|
@ -244,6 +244,13 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
if (this.filterParams['not[healthStatus]']) {
|
||||
filteredSearchValue.push({
|
||||
type: TOKEN_TYPE_HEALTH,
|
||||
value: { data: this.filterParams['not[healthStatus]'], operator: '!=' },
|
||||
});
|
||||
}
|
||||
|
||||
if (search) {
|
||||
filteredSearchValue.push(search);
|
||||
}
|
||||
|
@ -285,6 +292,7 @@ export default {
|
|||
'not[my_reaction_emoji]': this.filterParams.not.myReactionEmoji,
|
||||
'not[iteration_id]': this.filterParams.not.iterationId,
|
||||
'not[release_tag]': this.filterParams.not.releaseTag,
|
||||
'not[health_status]': this.filterParams.not.healthStatus,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
|
|
|
@ -360,14 +360,17 @@ export const filters = {
|
|||
},
|
||||
[TOKEN_TYPE_HEALTH]: {
|
||||
[API_PARAM]: {
|
||||
[NORMAL_FILTER]: 'healthStatus',
|
||||
[SPECIAL_FILTER]: 'healthStatus',
|
||||
[NORMAL_FILTER]: 'healthStatusFilter',
|
||||
[SPECIAL_FILTER]: 'healthStatusFilter',
|
||||
},
|
||||
[URL_PARAM]: {
|
||||
[OPERATOR_IS]: {
|
||||
[NORMAL_FILTER]: 'health_status',
|
||||
[SPECIAL_FILTER]: 'health_status',
|
||||
},
|
||||
[OPERATOR_NOT]: {
|
||||
[NORMAL_FILTER]: 'not[health_status]',
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOKEN_TYPE_CONTACT]: {
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
TOKEN_TYPE_MILESTONE,
|
||||
TOKEN_TYPE_RELEASE,
|
||||
TOKEN_TYPE_TYPE,
|
||||
TOKEN_TYPE_HEALTH,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
import {
|
||||
ALTERNATIVE_FILTER,
|
||||
|
@ -267,8 +268,13 @@ const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_R
|
|||
const isWildcardValue = (tokenType, value) =>
|
||||
wildcardTokens.includes(tokenType) && specialFilterValues.includes(value);
|
||||
|
||||
const isHealthStatusSpecialFilter = (tokenType, value) =>
|
||||
tokenType === TOKEN_TYPE_HEALTH && specialFilterValues.includes(value);
|
||||
|
||||
const requiresUpperCaseValue = (tokenType, value) =>
|
||||
tokenType === TOKEN_TYPE_TYPE || isWildcardValue(tokenType, value);
|
||||
tokenType === TOKEN_TYPE_TYPE ||
|
||||
isWildcardValue(tokenType, value) ||
|
||||
isHealthStatusSpecialFilter(tokenType, value);
|
||||
|
||||
const formatData = (token) => {
|
||||
if (requiresUpperCaseValue(token.type, token.value.data)) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import mermaid from 'mermaid';
|
||||
import mindmap from '@mermaid-js/mermaid-mindmap';
|
||||
import { getParameterByName } from '~/lib/utils/url_utility';
|
||||
|
||||
const setIframeRenderedSize = (h, w) => {
|
||||
|
@ -12,11 +13,10 @@ const drawDiagram = (source) => {
|
|||
// eslint-disable-next-line no-unsanitized/property
|
||||
element.innerHTML = svgCode;
|
||||
|
||||
const height = parseInt(element.firstElementChild.getAttribute('height'), 10);
|
||||
const width = parseInt(element.firstElementChild.style.maxWidth, 10);
|
||||
const { width, height } = element.firstElementChild.viewBox.baseVal;
|
||||
setIframeRenderedSize(height, width);
|
||||
};
|
||||
mermaid.mermaidAPI.render('mermaid', source, insertSvg);
|
||||
mermaid.mermaidAPI.renderAsync('mermaid', source, insertSvg);
|
||||
};
|
||||
|
||||
const darkModeEnabled = () => getParameterByName('darkMode') === 'true';
|
||||
|
@ -56,7 +56,13 @@ const addListener = () => {
|
|||
false,
|
||||
);
|
||||
};
|
||||
|
||||
addListener();
|
||||
initMermaid();
|
||||
mermaid
|
||||
.registerExternalDiagrams([mindmap])
|
||||
.then(() => {
|
||||
addListener();
|
||||
initMermaid();
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error;
|
||||
});
|
||||
export default {};
|
||||
|
|
|
@ -49,8 +49,6 @@ export default {
|
|||
:message="message"
|
||||
:title="s__('Member|Deny access')"
|
||||
:is-access-request="true"
|
||||
icon="close"
|
||||
button-category="primary"
|
||||
/>
|
||||
</div>
|
||||
</action-button-group>
|
||||
|
|
|
@ -40,7 +40,6 @@ export default {
|
|||
:title="$options.title"
|
||||
:aria-label="$options.title"
|
||||
icon="check"
|
||||
variant="confirm"
|
||||
type="submit"
|
||||
/>
|
||||
</gl-form>
|
||||
|
|
|
@ -41,8 +41,6 @@ export default {
|
|||
<remove-member-button
|
||||
:member-id="member.id"
|
||||
:message="message"
|
||||
icon="remove"
|
||||
button-category="primary"
|
||||
:title="s__('Member|Revoke invite')"
|
||||
is-invite
|
||||
/>
|
||||
|
|
|
@ -33,7 +33,6 @@ export default {
|
|||
:title="$options.title"
|
||||
:aria-label="$options.title"
|
||||
icon="leave"
|
||||
variant="danger"
|
||||
/>
|
||||
<leave-modal :member="member" />
|
||||
</div>
|
||||
|
|
|
@ -32,7 +32,6 @@ export default {
|
|||
<template>
|
||||
<gl-button
|
||||
v-gl-tooltip.hover
|
||||
variant="danger"
|
||||
:title="$options.i18n.buttonTitle"
|
||||
:aria-label="$options.i18n.buttonTitle"
|
||||
icon="remove"
|
||||
|
|
|
@ -25,23 +25,7 @@ export default {
|
|||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: undefined,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
buttonCategory: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'secondary',
|
||||
required: true,
|
||||
},
|
||||
isAccessRequest: {
|
||||
type: Boolean,
|
||||
|
@ -89,13 +73,10 @@ export default {
|
|||
<template>
|
||||
<gl-button
|
||||
v-gl-tooltip
|
||||
variant="danger"
|
||||
:category="buttonCategory"
|
||||
:title="title"
|
||||
:aria-label="title"
|
||||
:icon="icon"
|
||||
icon="remove"
|
||||
data-qa-selector="delete_member_button"
|
||||
@click="showRemoveMemberModal(modalData)"
|
||||
><template v-if="buttonText">{{ buttonText }}</template></gl-button
|
||||
>
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { __, s__, sprintf } from '~/locale';
|
||||
import { s__, __, sprintf } from '~/locale';
|
||||
import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils';
|
||||
import ActionButtonGroup from './action_button_group.vue';
|
||||
import LeaveButton from './leave_button.vue';
|
||||
|
@ -7,6 +7,9 @@ import RemoveMemberButton from './remove_member_button.vue';
|
|||
|
||||
export default {
|
||||
name: 'UserActionButtons',
|
||||
i18n: {
|
||||
title: __('Remove member'),
|
||||
},
|
||||
components: {
|
||||
ActionButtonGroup,
|
||||
RemoveMemberButton,
|
||||
|
@ -23,10 +26,6 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isInvitedUser: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
permissions: {
|
||||
type: Object,
|
||||
required: true,
|
||||
|
@ -60,15 +59,6 @@ export default {
|
|||
obstacles: parseUserDeletionObstacles(this.member.user),
|
||||
};
|
||||
},
|
||||
removeMemberButtonText() {
|
||||
return this.isInvitedUser ? null : __('Remove member');
|
||||
},
|
||||
removeMemberButtonIcon() {
|
||||
return this.isInvitedUser ? 'remove' : '';
|
||||
},
|
||||
removeMemberButtonCategory() {
|
||||
return this.isInvitedUser ? 'primary' : 'secondary';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -83,9 +73,7 @@ export default {
|
|||
:member-type="member.type"
|
||||
:user-deletion-obstacles="userDeletionObstaclesUserData"
|
||||
:message="message"
|
||||
:icon="removeMemberButtonIcon"
|
||||
:button-text="removeMemberButtonText"
|
||||
:button-category="removeMemberButtonCategory"
|
||||
:title="$options.i18n.title"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="permissions.canOverride && !member.isOverridden" class="gl-px-1">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script>
|
||||
import { GlSprintf } from '@gitlab/ui';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
import UserDate from '~/vue_shared/components/user_date.vue';
|
||||
|
||||
export default {
|
||||
name: 'CreatedAt',
|
||||
components: { GlSprintf, TimeAgoTooltip },
|
||||
components: { GlSprintf, UserDate },
|
||||
props: {
|
||||
date: {
|
||||
type: String,
|
||||
|
@ -29,12 +29,12 @@ export default {
|
|||
<span>
|
||||
<gl-sprintf v-if="showCreatedBy" :message="s__('Members|%{time} by %{user}')">
|
||||
<template #time>
|
||||
<time-ago-tooltip :time="date" />
|
||||
<user-date :date="date" />
|
||||
</template>
|
||||
<template #user>
|
||||
<a :href="createdBy.webUrl">{{ createdBy.name }}</a>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
<time-ago-tooltip v-else :time="date" />
|
||||
<user-date v-else :date="date" />
|
||||
</span>
|
||||
</template>
|
||||
|
|
|
@ -32,10 +32,6 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
isInvitedUser: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
actionButtonComponent() {
|
||||
|
@ -60,6 +56,5 @@ export default {
|
|||
:member="member"
|
||||
:permissions="permissions"
|
||||
:is-current-user="isCurrentUser"
|
||||
:is-invited-user="isInvitedUser"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<script>
|
||||
import UserDate from '~/vue_shared/components/user_date.vue';
|
||||
|
||||
export default {
|
||||
components: { UserDate },
|
||||
props: {
|
||||
member: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
userCreated() {
|
||||
return this.member.user?.createdAt;
|
||||
},
|
||||
lastActivity() {
|
||||
return this.member.user?.lastActivityOn;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="userCreated">
|
||||
<strong>{{ s__('Members|User created') }}:</strong>
|
||||
<user-date :date="userCreated" />
|
||||
</div>
|
||||
<div v-if="member.createdAt">
|
||||
<strong>{{ s__('Members|Access granted') }}:</strong>
|
||||
<user-date :date="member.createdAt" />
|
||||
</div>
|
||||
<div v-if="lastActivity">
|
||||
<strong>{{ s__('Members|Last activity') }}:</strong>
|
||||
<user-date :date="lastActivity" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,11 +1,19 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import { GlSprintf, GlTooltipDirective } from '@gitlab/ui';
|
||||
import { s__, __ } from '~/locale';
|
||||
|
||||
export default {
|
||||
name: 'MemberSource',
|
||||
i18n: {
|
||||
inherited: __('Inherited'),
|
||||
directMember: __('Direct member'),
|
||||
directMemberWithCreatedBy: s__('Members|Direct member by %{createdBy}'),
|
||||
inheritedMemberWithCreatedBy: s__('Members|%{group} by %{createdBy}'),
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
components: { GlSprintf },
|
||||
props: {
|
||||
memberSource: {
|
||||
type: Object,
|
||||
|
@ -15,13 +23,40 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
createdBy: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showCreatedBy() {
|
||||
return this.createdBy?.name && this.createdBy?.webUrl;
|
||||
},
|
||||
messageWithCreatedBy() {
|
||||
return this.isDirectMember
|
||||
? this.$options.i18n.directMemberWithCreatedBy
|
||||
: this.$options.i18n.inheritedMemberWithCreatedBy;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-if="isDirectMember">{{ __('Direct member') }}</span>
|
||||
<a v-else v-gl-tooltip.hover :title="__('Inherited')" :href="memberSource.webUrl">{{
|
||||
<span v-if="showCreatedBy">
|
||||
<gl-sprintf :message="messageWithCreatedBy">
|
||||
<template #group>
|
||||
<a v-gl-tooltip.hover="$options.i18n.inherited" :href="memberSource.webUrl">{{
|
||||
memberSource.fullName
|
||||
}}</a>
|
||||
</template>
|
||||
<template #createdBy>
|
||||
<a :href="createdBy.webUrl">{{ createdBy.name }}</a>
|
||||
</template>
|
||||
</gl-sprintf>
|
||||
</span>
|
||||
<span v-else-if="isDirectMember">{{ $options.i18n.directMember }}</span>
|
||||
<a v-else v-gl-tooltip.hover="$options.i18n.inherited" :href="memberSource.webUrl">{{
|
||||
memberSource.fullName
|
||||
}}</a>
|
||||
</template>
|
||||
|
|
|
@ -4,12 +4,10 @@ import { mapState } from 'vuex';
|
|||
import MembersTableCell from 'ee_else_ce/members/components/table/members_table_cell.vue';
|
||||
import { canUnban, canOverride, canRemove, canResend, canUpdate } from 'ee_else_ce/members/utils';
|
||||
import { mergeUrlParams } from '~/lib/utils/url_utility';
|
||||
import UserDate from '~/vue_shared/components/user_date.vue';
|
||||
import {
|
||||
FIELD_KEY_ACTIONS,
|
||||
FIELDS,
|
||||
ACTIVE_TAB_QUERY_PARAM_NAME,
|
||||
TAB_QUERY_PARAM_VALUES,
|
||||
MEMBER_STATE_AWAITING,
|
||||
MEMBER_STATE_ACTIVE,
|
||||
USER_STATE_BLOCKED,
|
||||
|
@ -23,6 +21,7 @@ import ExpirationDatepicker from './expiration_datepicker.vue';
|
|||
import MemberActionButtons from './member_action_buttons.vue';
|
||||
import MemberAvatar from './member_avatar.vue';
|
||||
import MemberSource from './member_source.vue';
|
||||
import MemberActivity from './member_activity.vue';
|
||||
import RoleDropdown from './role_dropdown.vue';
|
||||
|
||||
export default {
|
||||
|
@ -40,7 +39,7 @@ export default {
|
|||
RemoveGroupLinkModal,
|
||||
RemoveMemberModal,
|
||||
ExpirationDatepicker,
|
||||
UserDate,
|
||||
MemberActivity,
|
||||
LdapOverrideConfirmationModal: () =>
|
||||
import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'),
|
||||
},
|
||||
|
@ -80,9 +79,6 @@ export default {
|
|||
|
||||
return paramName && currentPage && perPage && totalItems;
|
||||
},
|
||||
isInvitedUser() {
|
||||
return this.tabQueryParamValue === TAB_QUERY_PARAM_VALUES.invite;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
hasActionButtons(member) {
|
||||
|
@ -249,7 +245,11 @@ export default {
|
|||
|
||||
<template #cell(source)="{ item: member }">
|
||||
<members-table-cell #default="{ isDirectMember }" :member="member">
|
||||
<member-source :is-direct-member="isDirectMember" :member-source="member.source" />
|
||||
<member-source
|
||||
:is-direct-member="isDirectMember"
|
||||
:member-source="member.source"
|
||||
:created-by="member.createdBy"
|
||||
/>
|
||||
</members-table-cell>
|
||||
</template>
|
||||
|
||||
|
@ -281,12 +281,8 @@ export default {
|
|||
</members-table-cell>
|
||||
</template>
|
||||
|
||||
<template #cell(userCreatedAt)="{ item: member }">
|
||||
<user-date :date="member.user.createdAt" />
|
||||
</template>
|
||||
|
||||
<template #cell(lastActivityOn)="{ item: member }">
|
||||
<user-date :date="member.user.lastActivityOn" />
|
||||
<template #cell(activity)="{ item: member }">
|
||||
<member-activity :member="member" />
|
||||
</template>
|
||||
|
||||
<template #cell(actions)="{ item: member }">
|
||||
|
@ -294,7 +290,6 @@ export default {
|
|||
<member-action-buttons
|
||||
:member-type="memberType"
|
||||
:is-current-user="isCurrentUser"
|
||||
:is-invited-user="isInvitedUser"
|
||||
:permissions="permissions"
|
||||
:member="member"
|
||||
/>
|
||||
|
|
|
@ -20,6 +20,7 @@ export const FIELD_KEY_MAX_ROLE = 'maxRole';
|
|||
export const FIELD_KEY_USER_CREATED_AT = 'userCreatedAt';
|
||||
export const FIELD_KEY_LAST_ACTIVITY_ON = 'lastActivityOn';
|
||||
export const FIELD_KEY_EXPIRATION = 'expiration';
|
||||
export const FIELD_KEY_ACTIVITY = 'activity';
|
||||
export const FIELD_KEY_LAST_SIGN_IN = 'lastSignIn';
|
||||
export const FIELD_KEY_ACTIONS = 'actions';
|
||||
|
||||
|
@ -41,8 +42,6 @@ export const FIELDS = [
|
|||
{
|
||||
key: FIELD_KEY_GRANTED,
|
||||
label: __('Access granted'),
|
||||
thClass: 'col-meta',
|
||||
tdClass: 'col-meta',
|
||||
sort: {
|
||||
asc: 'last_joined',
|
||||
desc: 'oldest_joined',
|
||||
|
@ -76,9 +75,15 @@ export const FIELDS = [
|
|||
thClass: 'col-expiration',
|
||||
tdClass: 'col-expiration',
|
||||
},
|
||||
{
|
||||
key: FIELD_KEY_ACTIVITY,
|
||||
label: s__('Members|Activity'),
|
||||
thClass: 'col-activity',
|
||||
tdClass: 'col-activity',
|
||||
},
|
||||
{
|
||||
key: FIELD_KEY_USER_CREATED_AT,
|
||||
label: __('Created on'),
|
||||
label: s__('Members|User created'),
|
||||
sort: {
|
||||
asc: 'oldest_created_user',
|
||||
desc: 'recent_created_user',
|
||||
|
|
|
@ -11,7 +11,7 @@ import { groupLinkRequestFormatter } from '~/members/utils';
|
|||
const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
|
||||
const APP_OPTIONS = {
|
||||
[MEMBER_TYPES.user]: {
|
||||
tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']),
|
||||
tableFields: SHARED_FIELDS.concat(['source', 'activity']),
|
||||
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
|
||||
tableSortableFields: [
|
||||
'account',
|
||||
|
|
|
@ -20,7 +20,7 @@ initImportProjectMembersTrigger();
|
|||
const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
|
||||
initMembersApp(document.querySelector('.js-project-members-list-app'), {
|
||||
[MEMBER_TYPES.user]: {
|
||||
tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']),
|
||||
tableFields: SHARED_FIELDS.concat(['source', 'activity']),
|
||||
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
|
||||
tableSortableFields: [
|
||||
'account',
|
||||
|
|
|
@ -9,7 +9,7 @@ const INTERVALS = {
|
|||
|
||||
export const FILE_SYMLINK_MODE = '120000';
|
||||
|
||||
export const SHORT_DATE_FORMAT = 'd mmm, yyyy';
|
||||
export const SHORT_DATE_FORMAT = 'mmm dd, yyyy';
|
||||
|
||||
export const ISO_SHORT_FORMAT = 'yyyy-mm-dd';
|
||||
|
||||
|
|
|
@ -76,6 +76,10 @@
|
|||
width: px-to-rem(200px);
|
||||
}
|
||||
|
||||
.col-activity {
|
||||
width: px-to-rem(250px);
|
||||
}
|
||||
|
||||
.col-actions {
|
||||
width: px-to-rem(65px);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class Import::BulkImportsController < ApplicationController
|
||||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
before_action :ensure_group_import_enabled
|
||||
before_action :ensure_bulk_import_enabled
|
||||
before_action :verify_blocked_uri, only: :status
|
||||
|
||||
feature_category :importers
|
||||
|
@ -118,8 +118,8 @@ class Import::BulkImportsController < ApplicationController
|
|||
]
|
||||
end
|
||||
|
||||
def ensure_group_import_enabled
|
||||
render_404 unless ::BulkImports::Features.enabled?
|
||||
def ensure_bulk_import_enabled
|
||||
render_404 unless Gitlab::CurrentSettings.bulk_import_enabled?
|
||||
end
|
||||
|
||||
def access_token_key
|
||||
|
|
|
@ -16,9 +16,8 @@
|
|||
|
||||
#import-group-pane.tab-pane
|
||||
- if import_sources_enabled?
|
||||
- if BulkImports::Features.enabled?
|
||||
= render 'import_group_from_another_instance_panel'
|
||||
.gl-mt-7.gl-border-b-solid.gl-border-gray-100.gl-border-1
|
||||
= render 'import_group_from_another_instance_panel'
|
||||
.gl-mt-7.gl-border-b-solid.gl-border-gray-100.gl-border-1
|
||||
= render 'import_group_from_file_panel'
|
||||
- else
|
||||
.nothing-here-block
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
name: bulk_import
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42704
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/255310
|
||||
milestone: '13.5'
|
||||
type: development
|
||||
group: group::import
|
||||
default_enabled: true
|
|
@ -34,7 +34,8 @@ module.exports = {
|
|||
'pikaday',
|
||||
'@gitlab/at.js',
|
||||
'jed',
|
||||
'mermaid',
|
||||
'mermaid/dist/mermaid.esm.mjs',
|
||||
'@mermaid-js/mermaid-mindmap/dist/mermaid-mindmap.esm.mjs',
|
||||
'katex',
|
||||
'three',
|
||||
'select2',
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class IndexMembersOnMemberNamespaceIdCompound < Gitlab::Database::Migration[2.1]
|
||||
INDEX_NAME = 'index_members_on_member_namespace_id_compound'
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
prepare_async_index(
|
||||
:members,
|
||||
[:member_namespace_id, :type, :requested_at, :id],
|
||||
name: INDEX_NAME
|
||||
)
|
||||
end
|
||||
|
||||
def down
|
||||
remove_concurrent_index_by_name :members, INDEX_NAME
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
8e9bb800a2eab9f5d5a3b4f3835b6c4f21ec861a5808a13bef8d496773a7799c
|
|
@ -1986,7 +1986,7 @@ On each node perform the following:
|
|||
{host: '10.6.0.53', port: 26379},
|
||||
]
|
||||
|
||||
## Second cluster that will host the persistent queues, shared state, and actionable
|
||||
## Second cluster that will host the persistent queues, shared state, and actioncable
|
||||
gitlab_rails['redis_queues_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
|
||||
gitlab_rails['redis_shared_state_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
|
||||
gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'
|
||||
|
|
|
@ -330,7 +330,7 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" \
|
|||
```
|
||||
|
||||
This action doesn't delete blobs. To delete them and recycle disk space,
|
||||
[run the garbage collection](https://docs.gitlab.com/omnibus/maintenance/index.html#removing-unused-layers-not-referenced-by-manifests).
|
||||
[run the garbage collection](../administration/packages/container_registry.md#container-registry-garbage-collection).
|
||||
|
||||
## Delete registry repository tags in bulk
|
||||
|
||||
|
@ -369,7 +369,7 @@ if successful, and performs the following operations:
|
|||
These operations are executed asynchronously and can take time to get executed.
|
||||
You can run this at most once an hour for a given container repository. This
|
||||
action doesn't delete blobs. To delete them and recycle disk space,
|
||||
[run the garbage collection](https://docs.gitlab.com/omnibus/maintenance/index.html#removing-unused-layers-not-referenced-by-manifests).
|
||||
[run the garbage collection](../administration/packages/container_registry.md#container-registry-garbage-collection).
|
||||
|
||||
WARNING:
|
||||
The number of tags deleted by this API is limited on GitLab.com
|
||||
|
|
|
@ -319,10 +319,8 @@ This operation is safe as there's no code using the table just yet.
|
|||
Dropping tables can be done safely using a post-deployment migration, but only
|
||||
if the application no longer uses the table.
|
||||
|
||||
Add the table to `DELETED_TABLES` in
|
||||
[gitlab_schema.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/gitlab_schema.rb),
|
||||
along with its `gitlab_schema`. Even though the table is deleted, it is still
|
||||
referenced in database migrations.
|
||||
Add the table to [`db/docs/deleted_tables`](https://gitlab.com/gitlab-org/gitlab/-/tree/master/db/docs/deleted_tables) using the process described in [database dictionary](database_dictionary.md#dropping-tables).
|
||||
Even though the table is deleted, it is still referenced in database migrations.
|
||||
|
||||
## Renaming Tables
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ For the `geo` database, the dictionary files are stored under `ee/db/docs/`.
|
|||
## Example dictionary file
|
||||
|
||||
```yaml
|
||||
---
|
||||
----
|
||||
table_name: terraform_states
|
||||
classes:
|
||||
- Terraform::State
|
||||
|
@ -28,45 +28,110 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26619
|
|||
milestone: '13.0'
|
||||
```
|
||||
|
||||
## Schema
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|----------------------------|---------------|----------|-----------------------------------------------------------------------------------|
|
||||
| `table_name` / `view_name` | String | yes | Database table name or view name |
|
||||
| `classes` | Array(String) | no | List of classes that are associated to this table or view. |
|
||||
| `feature_categories` | Array(String) | yes | List of feature categories using this table or view. |
|
||||
| `description` | String | no | Text description of the information stored in the table or view, and its purpose. |
|
||||
| `introduced_by_url` | URL | no | URL to the merge request or commit which introduced this table or view. |
|
||||
| `milestone` | String | no | The milestone that introduced this table or view. |
|
||||
| `gitlab_schema` | String | yes | GitLab schema name. |
|
||||
|
||||
## Adding tables
|
||||
|
||||
When adding a new table, create a new file under `db/docs/` for the `main` and `ci` databases.
|
||||
For the `geo` database use `ee/db/docs/`.
|
||||
Name the file as `<table_name>.yml`, containing as much information as you know about the table.
|
||||
### Schema
|
||||
|
||||
Include this file in the commit with the migration that creates the table.
|
||||
| Attribute | Type | Required | Description |
|
||||
|----------------------------|---------------|----------|-------------|
|
||||
| `table_name` | String | yes | Database table name. |
|
||||
| `classes` | Array(String) | no | List of classes that are associated to this table. |
|
||||
| `feature_categories` | Array(String) | yes | List of feature categories using this table. |
|
||||
| `description` | String | no | Text description of the information stored in the table, and its purpose. |
|
||||
| `introduced_by_url` | URL | no | URL to the merge request or commit which introduced this table. |
|
||||
| `milestone` | String | no | The milestone that introduced this table. |
|
||||
| `gitlab_schema` | String | yes | GitLab schema name. |
|
||||
|
||||
### Process
|
||||
|
||||
When adding a table, you should:
|
||||
|
||||
1. Create a new file for this table in the appropriate directory:
|
||||
- `gitlab_main` table: `db/docs/`
|
||||
- `gitlab_ci` table: `db/docs/`
|
||||
- `gitlab_shared` table: `db/docs/`
|
||||
- `gitlab_geo` table: `ee/db/docs/`
|
||||
1. Name the file `<table_name>.yml`, and include as much information as you know about the table.
|
||||
1. Include this file in the commit with the migration that creates the table.
|
||||
|
||||
## Dropping tables
|
||||
|
||||
When dropping a table, you must remove the metadata file from `db/docs/` for `main` and `ci` databases.
|
||||
For the `geo` database, you must remove the file from `ee/db/docs/`.
|
||||
Use the same commit with the migration that drops the table.
|
||||
### Schema
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|----------------------------|---------------|----------|-------------|
|
||||
| `table_name` | String | yes | Database table name. |
|
||||
| `classes` | Array(String) | no | List of classes that are associated to this table. |
|
||||
| `feature_categories` | Array(String) | yes | List of feature categories using this table. |
|
||||
| `description` | String | no | Text description of the information stored in the table, and its purpose. |
|
||||
| `introduced_by_url` | URL | no | URL to the merge request or commit which introduced this table. |
|
||||
| `milestone` | String | no | The milestone that introduced this table. |
|
||||
| `gitlab_schema` | String | yes | GitLab schema name. |
|
||||
| `removed_by_url` | String | yes | URL to the merge request or commit which removed this table. |
|
||||
| `removed_in_milestone` | String | yes | The milestone that removes this table. |
|
||||
|
||||
### Process
|
||||
|
||||
When dropping a table, you should:
|
||||
|
||||
1. Move the dictionary file for this table to the `deleted_tables` directory:
|
||||
- `gitlab_main` table: `db/docs/deleted_tables/`
|
||||
- `gitlab_ci` table: `db/docs/deleted_tables/`
|
||||
- `gitlab_shared` table: `db/docs/deleted_tables/`
|
||||
- `gitlab_geo` table: `ee/db/docs/deleted_tables/`
|
||||
1. Add the fields `removed_by_url` and `removed_in_milestone` to the dictionary file.
|
||||
1. Include this change in the commit with the migration that drops the table.
|
||||
|
||||
## Adding views
|
||||
|
||||
### Schema
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|----------------------------|---------------|----------|-------------|
|
||||
| `table_name` | String | yes | Database view name. |
|
||||
| `classes` | Array(String) | no | List of classes that are associated to this view. |
|
||||
| `feature_categories` | Array(String) | yes | List of feature categories using this view. |
|
||||
| `description` | String | no | Text description of the information stored in the view, and its purpose. |
|
||||
| `introduced_by_url` | URL | no | URL to the merge request or commit which introduced this view. |
|
||||
| `milestone` | String | no | The milestone that introduced this view. |
|
||||
| `gitlab_schema` | String | yes | GitLab schema name. |
|
||||
|
||||
### Process
|
||||
|
||||
When adding a new view, you should:
|
||||
|
||||
1. Create a new file for this view in the appropriate directory:
|
||||
- `main` database: `db/docs/views/`
|
||||
- `ci` database: `db/docs/views/`
|
||||
- `geo` database: `ee/db/docs/views/`
|
||||
- `gitlab_main` view: `db/docs/views/`
|
||||
- `gitlab_ci` view: `db/docs/views/`
|
||||
- `gitlab_shared` view: `db/docs/views/`
|
||||
- `gitlab_geo` view: `ee/db/docs/views/`
|
||||
1. Name the file `<view_name>.yml`, and include as much information as you know about the view.
|
||||
1. Include this file in the commit with the migration that creates the view.
|
||||
|
||||
## Dropping views
|
||||
|
||||
When dropping a view, you must remove the metadata file from `db/docs/views/`.
|
||||
For the `geo` database, you must remove the file from `ee/db/docs/views/`.
|
||||
Use the same commit with the migration that drops the view.
|
||||
## Schema
|
||||
|
||||
| Attribute | Type | Required | Description |
|
||||
|----------------------------|---------------|----------|-------------|
|
||||
| `view_name` | String | yes | Database view name. |
|
||||
| `classes` | Array(String) | no | List of classes that are associated to this view. |
|
||||
| `feature_categories` | Array(String) | yes | List of feature categories using this view. |
|
||||
| `description` | String | no | Text description of the information stored in the view, and its purpose. |
|
||||
| `introduced_by_url` | URL | no | URL to the merge request or commit which introduced this view. |
|
||||
| `milestone` | String | no | The milestone that introduced this view. |
|
||||
| `gitlab_schema` | String | yes | GitLab schema name. |
|
||||
| `removed_by_url` | String | yes | URL to the merge request or commit which removed this view. |
|
||||
| `removed_in_milestone` | String | yes | The milestone that removes this view. |
|
||||
|
||||
### Process
|
||||
|
||||
When dropping a view, you should:
|
||||
|
||||
1. Move the dictionary file for this table to the `deleted_views` directory:
|
||||
- `gitlab_main` view: `db/docs/deleted_views/`
|
||||
- `gitlab_ci` view: `db/docs/deleted_views/`
|
||||
- `gitlab_shared` view: `db/docs/deleted_views/`
|
||||
- `gitlab_geo` view: `ee/db/docs/deleted_views/`
|
||||
1. Add the fields `removed_by_url` and `removed_in_milestone` to the dictionary file.
|
||||
1. Include this change in the commit with the migration that drops the view.
|
||||
|
|
|
@ -37,6 +37,7 @@ this feature, ask an administrator to [enable the feature flag](../../../adminis
|
|||
Prerequisites:
|
||||
|
||||
- Network connection between instances or GitLab.com. Must support HTTPS.
|
||||
- Both GitLab instances have migration enabled in application settings by instance administrator.
|
||||
- Owner role on the top-level group to migrate.
|
||||
|
||||
You can import top-level groups to:
|
||||
|
|
|
@ -33,7 +33,7 @@ module API
|
|||
end
|
||||
|
||||
before do
|
||||
not_found! unless ::BulkImports::Features.enabled?
|
||||
not_found! unless Gitlab::CurrentSettings.bulk_import_enabled?
|
||||
|
||||
authenticate!
|
||||
end
|
||||
|
|
|
@ -64,67 +64,73 @@ module API
|
|||
end
|
||||
end
|
||||
|
||||
desc 'Start relations export' do
|
||||
detail 'This feature was introduced in GitLab 13.12'
|
||||
tags %w[group_export]
|
||||
success code: 202
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
end
|
||||
post ':id/export_relations' do
|
||||
response = ::BulkImports::ExportService.new(portable: user_group, user: current_user).execute
|
||||
|
||||
if response.success?
|
||||
accepted!
|
||||
else
|
||||
render_api_error!(message: 'Group relations export could not be started.')
|
||||
resource do
|
||||
before do
|
||||
not_found! unless Gitlab::CurrentSettings.bulk_import_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Download relations export' do
|
||||
detail 'This feature was introduced in GitLab 13.12'
|
||||
produces %w[application/octet-stream application/json]
|
||||
tags %w[group_export]
|
||||
success code: 200
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
end
|
||||
params do
|
||||
requires :relation, type: String, desc: 'Group relation name'
|
||||
end
|
||||
get ':id/export_relations/download' do
|
||||
export = user_group.bulk_import_exports.find_by_relation(params[:relation])
|
||||
file = export&.upload&.export_file
|
||||
|
||||
if file
|
||||
present_carrierwave_file!(file)
|
||||
else
|
||||
render_api_error!('404 Not found', 404)
|
||||
desc 'Start relations export' do
|
||||
detail 'This feature was introduced in GitLab 13.12'
|
||||
tags %w[group_export]
|
||||
success code: 202
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
end
|
||||
end
|
||||
post ':id/export_relations' do
|
||||
response = ::BulkImports::ExportService.new(portable: user_group, user: current_user).execute
|
||||
|
||||
desc 'Relations export status' do
|
||||
detail 'This feature was introduced in GitLab 13.12'
|
||||
is_array true
|
||||
tags %w[group_export]
|
||||
success code: 200, model: Entities::BulkImports::ExportStatus
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
end
|
||||
get ':id/export_relations/status' do
|
||||
present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus
|
||||
if response.success?
|
||||
accepted!
|
||||
else
|
||||
render_api_error!(message: 'Group relations export could not be started.')
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Download relations export' do
|
||||
detail 'This feature was introduced in GitLab 13.12'
|
||||
produces %w[application/octet-stream application/json]
|
||||
tags %w[group_export]
|
||||
success code: 200
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
end
|
||||
params do
|
||||
requires :relation, type: String, desc: 'Group relation name'
|
||||
end
|
||||
get ':id/export_relations/download' do
|
||||
export = user_group.bulk_import_exports.find_by_relation(params[:relation])
|
||||
file = export&.upload&.export_file
|
||||
|
||||
if file
|
||||
present_carrierwave_file!(file)
|
||||
else
|
||||
render_api_error!('404 Not found', 404)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Relations export status' do
|
||||
detail 'This feature was introduced in GitLab 13.12'
|
||||
is_array true
|
||||
tags %w[group_export]
|
||||
success code: 200, model: Entities::BulkImports::ExportStatus
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
end
|
||||
get ':id/export_relations/status' do
|
||||
present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,109 +5,114 @@ module API
|
|||
feature_category :importers
|
||||
urgency :low
|
||||
|
||||
before do
|
||||
not_found! unless Gitlab::CurrentSettings.project_export_enabled?
|
||||
authorize_admin_project
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
|
||||
end
|
||||
resource :projects, requirements: { id: %r{[^/]+} } do
|
||||
desc 'Get export status' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success code: 200, model: Entities::ProjectExportStatus
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
tags ['project_export']
|
||||
end
|
||||
get ':id/export' do
|
||||
present user_project, with: Entities::ProjectExportStatus
|
||||
end
|
||||
resource do
|
||||
before do
|
||||
not_found! unless Gitlab::CurrentSettings.project_export_enabled?
|
||||
|
||||
desc 'Download export' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success code: 200
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
tags ['project_export']
|
||||
produces %w[application/octet-stream application/json]
|
||||
end
|
||||
get ':id/export/download' do
|
||||
check_rate_limit! :project_download_export, scope: [current_user, user_project.namespace]
|
||||
authorize_admin_project
|
||||
end
|
||||
|
||||
if user_project.export_file_exists?
|
||||
if user_project.export_archive_exists?
|
||||
present_carrierwave_file!(user_project.export_file)
|
||||
desc 'Get export status' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success code: 200, model: Entities::ProjectExportStatus
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
tags ['project_export']
|
||||
end
|
||||
get ':id/export' do
|
||||
present user_project, with: Entities::ProjectExportStatus
|
||||
end
|
||||
|
||||
desc 'Download export' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success code: 200
|
||||
failure [
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
tags ['project_export']
|
||||
produces %w[application/octet-stream application/json]
|
||||
end
|
||||
get ':id/export/download' do
|
||||
check_rate_limit! :project_download_export, scope: [current_user, user_project.namespace]
|
||||
|
||||
if user_project.export_file_exists?
|
||||
if user_project.export_archive_exists?
|
||||
present_carrierwave_file!(user_project.export_file)
|
||||
else
|
||||
render_api_error!('The project export file is not available yet', 404)
|
||||
end
|
||||
else
|
||||
render_api_error!('The project export file is not available yet', 404)
|
||||
end
|
||||
else
|
||||
render_api_error!('404 Not found or has expired', 404)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Start export' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success code: 202
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 429, message: 'Too many requests' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
tags ['project_export']
|
||||
end
|
||||
params do
|
||||
optional :description, type: String, desc: 'Override the project description'
|
||||
optional :upload, type: Hash do
|
||||
optional :url, type: String, desc: 'The URL to upload the project'
|
||||
optional :http_method, type: String, default: 'PUT', values: %w[PUT POST],
|
||||
desc: 'HTTP method to upload the exported project'
|
||||
end
|
||||
end
|
||||
post ':id/export' do
|
||||
check_rate_limit! :project_export, scope: current_user
|
||||
|
||||
user_project.remove_exports
|
||||
|
||||
project_export_params = declared_params(include_missing: false)
|
||||
after_export_params = project_export_params.delete(:upload) || {}
|
||||
|
||||
export_strategy = if after_export_params[:url].present?
|
||||
params = after_export_params.slice(:url, :http_method).symbolize_keys
|
||||
|
||||
Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy.new(**params)
|
||||
end
|
||||
|
||||
if export_strategy&.invalid?
|
||||
render_validation_error!(export_strategy)
|
||||
else
|
||||
begin
|
||||
user_project.add_export_job(current_user: current_user,
|
||||
after_export_strategy: export_strategy,
|
||||
params: project_export_params)
|
||||
rescue Project::ExportLimitExceeded => e
|
||||
render_api_error!(e.message, 400)
|
||||
render_api_error!('404 Not found or has expired', 404)
|
||||
end
|
||||
end
|
||||
|
||||
accepted!
|
||||
desc 'Start export' do
|
||||
detail 'This feature was introduced in GitLab 10.6.'
|
||||
success code: 202
|
||||
failure [
|
||||
{ code: 400, message: 'Bad request' },
|
||||
{ code: 401, message: 'Unauthorized' },
|
||||
{ code: 403, message: 'Forbidden' },
|
||||
{ code: 404, message: 'Not found' },
|
||||
{ code: 429, message: 'Too many requests' },
|
||||
{ code: 503, message: 'Service unavailable' }
|
||||
]
|
||||
tags ['project_export']
|
||||
end
|
||||
params do
|
||||
optional :description, type: String, desc: 'Override the project description'
|
||||
optional :upload, type: Hash do
|
||||
optional :url, type: String, desc: 'The URL to upload the project'
|
||||
optional :http_method, type: String, default: 'PUT', values: %w[PUT POST],
|
||||
desc: 'HTTP method to upload the exported project'
|
||||
end
|
||||
end
|
||||
post ':id/export' do
|
||||
check_rate_limit! :project_export, scope: current_user
|
||||
|
||||
user_project.remove_exports
|
||||
|
||||
project_export_params = declared_params(include_missing: false)
|
||||
after_export_params = project_export_params.delete(:upload) || {}
|
||||
|
||||
export_strategy = if after_export_params[:url].present?
|
||||
params = after_export_params.slice(:url, :http_method).symbolize_keys
|
||||
|
||||
Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy.new(**params)
|
||||
end
|
||||
|
||||
if export_strategy&.invalid?
|
||||
render_validation_error!(export_strategy)
|
||||
else
|
||||
begin
|
||||
user_project.add_export_job(current_user: current_user,
|
||||
after_export_strategy: export_strategy,
|
||||
params: project_export_params)
|
||||
rescue Project::ExportLimitExceeded => e
|
||||
render_api_error!(e.message, 400)
|
||||
end
|
||||
end
|
||||
|
||||
accepted!
|
||||
end
|
||||
end
|
||||
|
||||
resource do
|
||||
before do
|
||||
not_found! unless ::Feature.enabled?(:bulk_import)
|
||||
not_found! unless Gitlab::CurrentSettings.bulk_import_enabled?
|
||||
|
||||
authorize_admin_project
|
||||
end
|
||||
|
||||
desc 'Start relations export' do
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
module BulkImports
|
||||
module Features
|
||||
def self.enabled?
|
||||
::Feature.enabled?(:bulk_import)
|
||||
end
|
||||
|
||||
def self.project_migration_enabled?(destination_namespace = nil)
|
||||
if destination_namespace.present?
|
||||
root_ancestor = Namespace.find_by_full_path(destination_namespace)&.root_ancestor
|
||||
|
|
|
@ -17,42 +17,6 @@ module Gitlab
|
|||
module GitlabSchema
|
||||
DICTIONARY_PATH = 'db/docs/'
|
||||
|
||||
# These tables are deleted/renamed, but still referenced by migrations.
|
||||
# This is needed for now, but should be removed in the future
|
||||
DELETED_TABLES = {
|
||||
# main tables
|
||||
'alerts_service_data' => :gitlab_main,
|
||||
'analytics_devops_adoption_segment_selections' => :gitlab_main,
|
||||
'analytics_repository_file_commits' => :gitlab_main,
|
||||
'analytics_repository_file_edits' => :gitlab_main,
|
||||
'analytics_repository_files' => :gitlab_main,
|
||||
'audit_events_archived' => :gitlab_main,
|
||||
'backup_labels' => :gitlab_main,
|
||||
'clusters_applications_fluentd' => :gitlab_main,
|
||||
'forked_project_links' => :gitlab_main,
|
||||
'issue_milestones' => :gitlab_main,
|
||||
'merge_request_milestones' => :gitlab_main,
|
||||
'namespace_onboarding_actions' => :gitlab_main,
|
||||
'services' => :gitlab_main,
|
||||
'terraform_state_registry' => :gitlab_main,
|
||||
'tmp_fingerprint_sha256_migration' => :gitlab_main, # used by lib/gitlab/background_migration/migrate_fingerprint_sha256_within_keys.rb
|
||||
'web_hook_logs_archived' => :gitlab_main,
|
||||
'vulnerability_export_registry' => :gitlab_main,
|
||||
'vulnerability_finding_fingerprints' => :gitlab_main,
|
||||
'vulnerability_export_verification_status' => :gitlab_main,
|
||||
|
||||
# CI tables
|
||||
'ci_build_trace_sections' => :gitlab_ci,
|
||||
'ci_build_trace_section_names' => :gitlab_ci,
|
||||
'ci_daily_report_results' => :gitlab_ci,
|
||||
'ci_test_cases' => :gitlab_ci,
|
||||
'ci_test_case_failures' => :gitlab_ci,
|
||||
|
||||
# leftovers from early implementation of partitioning
|
||||
'audit_events_part_5fc467ac26' => :gitlab_main,
|
||||
'web_hook_logs_part_0c5294f417' => :gitlab_main
|
||||
}.freeze
|
||||
|
||||
def self.table_schemas(tables)
|
||||
tables.map { |table| table_schema(table) }.to_set
|
||||
end
|
||||
|
@ -69,13 +33,13 @@ module Gitlab
|
|||
# strip partition number of a form `loose_foreign_keys_deleted_records_1`
|
||||
table_name.gsub!(/_[0-9]+$/, '')
|
||||
|
||||
# Tables that are properly mapped
|
||||
# Tables and views that are properly mapped
|
||||
if gitlab_schema = views_and_tables_to_schema[table_name]
|
||||
return gitlab_schema
|
||||
end
|
||||
|
||||
# Tables that are deleted, but we still need to reference them
|
||||
if gitlab_schema = DELETED_TABLES[table_name]
|
||||
# Tables and views that are deleted, but we still need to reference them
|
||||
if gitlab_schema = deleted_views_and_tables_to_schema[table_name]
|
||||
return gitlab_schema
|
||||
end
|
||||
|
||||
|
@ -106,29 +70,51 @@ module Gitlab
|
|||
[Rails.root.join(DICTIONARY_PATH, 'views', '*.yml')]
|
||||
end
|
||||
|
||||
def self.deleted_views_path_globs
|
||||
[Rails.root.join(DICTIONARY_PATH, 'deleted_views', '*.yml')]
|
||||
end
|
||||
|
||||
def self.deleted_tables_path_globs
|
||||
[Rails.root.join(DICTIONARY_PATH, 'deleted_tables', '*.yml')]
|
||||
end
|
||||
|
||||
def self.views_and_tables_to_schema
|
||||
@views_and_tables_to_schema ||= self.tables_to_schema.merge(self.views_to_schema)
|
||||
end
|
||||
|
||||
def self.tables_to_schema
|
||||
@tables_to_schema ||= Dir.glob(self.dictionary_path_globs).each_with_object({}) do |file_path, dic|
|
||||
data = YAML.load_file(file_path)
|
||||
def self.deleted_views_and_tables_to_schema
|
||||
@deleted_views_and_tables_to_schema ||= self.deleted_tables_to_schema.merge(self.deleted_views_to_schema)
|
||||
end
|
||||
|
||||
dic[data['table_name']] = data['gitlab_schema'].to_sym
|
||||
end
|
||||
def self.deleted_tables_to_schema
|
||||
@deleted_tables_to_schema ||= self.build_dictionary(self.deleted_tables_path_globs)
|
||||
end
|
||||
|
||||
def self.deleted_views_to_schema
|
||||
@deleted_views_to_schema ||= self.build_dictionary(self.deleted_views_path_globs)
|
||||
end
|
||||
|
||||
def self.tables_to_schema
|
||||
@tables_to_schema ||= self.build_dictionary(self.dictionary_path_globs)
|
||||
end
|
||||
|
||||
def self.views_to_schema
|
||||
@views_to_schema ||= Dir.glob(self.view_path_globs).each_with_object({}) do |file_path, dic|
|
||||
data = YAML.load_file(file_path)
|
||||
|
||||
dic[data['view_name']] = data['gitlab_schema'].to_sym
|
||||
end
|
||||
@views_to_schema ||= self.build_dictionary(self.view_path_globs)
|
||||
end
|
||||
|
||||
def self.schema_names
|
||||
@schema_names ||= self.views_and_tables_to_schema.values.to_set
|
||||
end
|
||||
|
||||
private_class_method def self.build_dictionary(path_globs)
|
||||
Dir.glob(path_globs).each_with_object({}) do |file_path, dic|
|
||||
data = YAML.load_file(file_path)
|
||||
|
||||
key_name = data['table_name'] || data['view_name']
|
||||
|
||||
dic[key_name] = data['gitlab_schema'].to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,7 +42,7 @@ module Gitlab
|
|||
def should_lock_writes_on_table?(table_name)
|
||||
# currently gitlab_schema represents only present existing tables, this is workaround for deleted tables
|
||||
# that should be skipped as they will be removed in a future migration.
|
||||
return false if Gitlab::Database::GitlabSchema::DELETED_TABLES[table_name]
|
||||
return false if Gitlab::Database::GitlabSchema.deleted_tables_to_schema[table_name]
|
||||
|
||||
table_schema = Gitlab::Database::GitlabSchema.table_schema(table_name.to_s, undefined: false)
|
||||
|
||||
|
|
|
@ -17,7 +17,8 @@ module Gitlab
|
|||
HTTP_ERRORS = HTTP_TIMEOUT_ERRORS + [
|
||||
EOFError, SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
|
||||
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH,
|
||||
Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep
|
||||
Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep,
|
||||
Net::HTTPBadResponse
|
||||
].freeze
|
||||
|
||||
DEFAULT_TIMEOUT_OPTIONS = {
|
||||
|
|
|
@ -25797,6 +25797,9 @@ msgstr[1] ""
|
|||
msgid "Membership"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|%{group} by %{createdBy}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|%{time} by %{user}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25806,6 +25809,12 @@ msgstr ""
|
|||
msgid "Members|2FA"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|Access granted"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|Activity"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|An error occurred while trying to enable LDAP override, please try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -25842,6 +25851,9 @@ msgstr ""
|
|||
msgid "Members|Direct"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|Direct member by %{createdBy}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|Disabled"
|
||||
msgstr ""
|
||||
|
||||
|
@ -25869,6 +25881,9 @@ msgstr ""
|
|||
msgid "Members|LDAP override enabled."
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|Last activity"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|Leave \"%{source}\""
|
||||
msgstr ""
|
||||
|
||||
|
@ -25896,6 +25911,9 @@ msgstr ""
|
|||
msgid "Members|Search invited"
|
||||
msgstr ""
|
||||
|
||||
msgid "Members|User created"
|
||||
msgstr ""
|
||||
|
||||
msgid "Member|Deny access"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
"@gitlab/ui": "52.6.1",
|
||||
"@gitlab/visual-review-tools": "1.7.3",
|
||||
"@gitlab/web-ide": "0.0.1-dev-20221217175648",
|
||||
"@mermaid-js/mermaid-mindmap": "^9.3.0",
|
||||
"@rails/actioncable": "6.1.4-7",
|
||||
"@rails/ujs": "6.1.4-7",
|
||||
"@sourcegraph/code-host-integration": "0.0.84",
|
||||
|
@ -147,7 +148,7 @@
|
|||
"marked": "^4.0.18",
|
||||
"mathjax": "3",
|
||||
"mdurl": "^1.0.1",
|
||||
"mermaid": "^9.1.3",
|
||||
"mermaid": "^9.3.0",
|
||||
"micromatch": "^4.0.5",
|
||||
"minimatch": "^3.0.4",
|
||||
"monaco-editor": "^0.30.1",
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Import::BulkImportsController do
|
||||
RSpec.describe Import::BulkImportsController, feature_category: :importers do
|
||||
let_it_be(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_application_setting(bulk_import_enabled: true)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
@ -326,9 +328,9 @@ RSpec.describe Import::BulkImportsController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when bulk_import feature flag is disabled' do
|
||||
context 'when feature is disabled' do
|
||||
before do
|
||||
stub_feature_flags(bulk_import: false)
|
||||
stub_application_setting(bulk_import_enabled: false)
|
||||
end
|
||||
|
||||
context 'POST configure' do
|
||||
|
|
|
@ -8,6 +8,7 @@ RSpec.shared_examples 'validate dictionary' do |objects, directory_path, require
|
|||
|
||||
let(:metadata_allowed_fields) do
|
||||
required_fields + %i[
|
||||
feature_categories
|
||||
classes
|
||||
description
|
||||
introduced_by_url
|
||||
|
@ -139,3 +140,19 @@ RSpec.describe 'Tables documentation', feature_category: :database do
|
|||
|
||||
include_examples 'validate dictionary', tables, directory_path, required_fields
|
||||
end
|
||||
|
||||
RSpec.describe 'Deleted tables documentation', feature_category: :database do
|
||||
directory_path = File.join('db', 'docs', 'deleted_tables')
|
||||
tables = Dir.glob(File.join(directory_path, '*.yml')).map { |f| File.basename(f, '.yml') }.sort.uniq
|
||||
required_fields = %i[table_name gitlab_schema removed_by_url removed_in_milestone]
|
||||
|
||||
include_examples 'validate dictionary', tables, directory_path, required_fields
|
||||
end
|
||||
|
||||
RSpec.describe 'Deleted views documentation', feature_category: :database do
|
||||
directory_path = File.join('db', 'docs', 'deleted_views')
|
||||
views = Dir.glob(File.join(directory_path, '*.yml')).map { |f| File.basename(f, '.yml') }.sort.uniq
|
||||
required_fields = %i[view_name gitlab_schema removed_by_url removed_in_milestone]
|
||||
|
||||
include_examples 'validate dictionary', views, directory_path, required_fields
|
||||
end
|
||||
|
|
|
@ -28,7 +28,7 @@ RSpec.describe 'Admin::Users', feature_category: :user_management do
|
|||
|
||||
expect(page).to have_content(current_user.email)
|
||||
expect(page).to have_content(current_user.name)
|
||||
expect(page).to have_content(current_user.created_at.strftime('%e %b, %Y'))
|
||||
expect(page).to have_content(current_user.created_at.strftime('%b %e, %Y'))
|
||||
expect(page).to have_content(user.email)
|
||||
expect(page).to have_content(user.name)
|
||||
expect(page).to have_content('Projects')
|
||||
|
|
|
@ -12,6 +12,8 @@ RSpec.describe 'Import/Export - GitLab migration history', :js, feature_category
|
|||
let_it_be(:failed_entity_2) { create(:bulk_import_entity, :failed, bulk_import: user_import_2) }
|
||||
|
||||
before do
|
||||
stub_application_setting(bulk_import_enabled: true)
|
||||
|
||||
gitlab_sign_in(user)
|
||||
|
||||
visit new_group_path
|
||||
|
|
|
@ -56,7 +56,7 @@ RSpec.describe 'Groups > Members > Sort members', :js, feature_category: :subgro
|
|||
expect(first_row.text).to include(owner.name)
|
||||
expect(second_row.text).to include(developer.name)
|
||||
|
||||
expect_sort_by('Created on', :asc)
|
||||
expect_sort_by('User created', :asc)
|
||||
end
|
||||
|
||||
it 'sorts by user created on descending' do
|
||||
|
@ -65,7 +65,7 @@ RSpec.describe 'Groups > Members > Sort members', :js, feature_category: :subgro
|
|||
expect(first_row.text).to include(developer.name)
|
||||
expect(second_row.text).to include(owner.name)
|
||||
|
||||
expect_sort_by('Created on', :desc)
|
||||
expect_sort_by('User created', :desc)
|
||||
end
|
||||
|
||||
it 'sorts by last activity ascending' do
|
||||
|
|
|
@ -48,7 +48,7 @@ RSpec.describe 'Projects > Members > Sorting', :js, feature_category: :subgroups
|
|||
expect(first_row.text).to have_content(maintainer.name)
|
||||
expect(second_row.text).to have_content(developer.name)
|
||||
|
||||
expect_sort_by('Created on', :asc)
|
||||
expect_sort_by('User created', :asc)
|
||||
end
|
||||
|
||||
it 'sorts by user created on descending' do
|
||||
|
@ -57,7 +57,7 @@ RSpec.describe 'Projects > Members > Sorting', :js, feature_category: :subgroups
|
|||
expect(first_row.text).to have_content(developer.name)
|
||||
expect(second_row.text).to have_content(maintainer.name)
|
||||
|
||||
expect_sort_by('Created on', :desc)
|
||||
expect_sort_by('User created', :desc)
|
||||
end
|
||||
|
||||
it 'sorts by last activity ascending' do
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('FormatDate component', () => {
|
|||
|
||||
it.each`
|
||||
date | dateFormat | output
|
||||
${mockDate} | ${undefined} | ${'13 Nov, 2020'}
|
||||
${mockDate} | ${undefined} | ${'Nov 13, 2020'}
|
||||
${null} | ${undefined} | ${'Never'}
|
||||
${undefined} | ${undefined} | ${'Never'}
|
||||
${mockDate} | ${ISO_SHORT_FORMAT} | ${'2020-11-13'}
|
||||
|
|
|
@ -139,6 +139,7 @@ describe('BoardFilteredSearch', () => {
|
|||
{ type: TOKEN_TYPE_ITERATION, value: { data: 'Any&3', operator: '=' } },
|
||||
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v1.0.0', operator: '=' } },
|
||||
{ type: TOKEN_TYPE_HEALTH, value: { data: 'onTrack', operator: '=' } },
|
||||
{ type: TOKEN_TYPE_HEALTH, value: { data: 'atRisk', operator: '!=' } },
|
||||
];
|
||||
jest.spyOn(urlUtility, 'updateHistory');
|
||||
findFilteredSearch().vm.$emit('onFilter', mockFilters);
|
||||
|
@ -147,7 +148,7 @@ describe('BoardFilteredSearch', () => {
|
|||
title: '',
|
||||
replace: true,
|
||||
url:
|
||||
'http://test.host/?author_username=root&label_name[]=label&label_name[]=label%262&assignee_username=root&milestone_title=New%20Milestone&iteration_id=Any&iteration_cadence_id=3&types=INCIDENT&weight=2&release_tag=v1.0.0&health_status=onTrack',
|
||||
'http://test.host/?not[health_status]=atRisk&author_username=root&label_name[]=label&label_name[]=label%262&assignee_username=root&milestone_title=New%20Milestone&iteration_id=Any&iteration_cadence_id=3&types=INCIDENT&weight=2&release_tag=v1.0.0&health_status=onTrack',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
TOKEN_TYPE_RELEASE,
|
||||
TOKEN_TYPE_TYPE,
|
||||
TOKEN_TYPE_WEIGHT,
|
||||
TOKEN_TYPE_HEALTH,
|
||||
} from '~/vue_shared/components/filtered_search_bar/constants';
|
||||
|
||||
export const getIssuesQueryResponse = {
|
||||
|
@ -170,6 +171,8 @@ export const locationSearch = [
|
|||
'not[weight]=3',
|
||||
'crm_contact_id=123',
|
||||
'crm_organization_id=456',
|
||||
'health_status=atRisk',
|
||||
'not[health_status]=onTrack',
|
||||
].join('&');
|
||||
|
||||
export const locationSearchWithSpecialValues = [
|
||||
|
@ -182,6 +185,7 @@ export const locationSearchWithSpecialValues = [
|
|||
'milestone_title=Upcoming',
|
||||
'epic_id=None',
|
||||
'weight=None',
|
||||
'health_status=None',
|
||||
].join('&');
|
||||
|
||||
export const filteredTokens = [
|
||||
|
@ -225,6 +229,8 @@ export const filteredTokens = [
|
|||
{ type: TOKEN_TYPE_WEIGHT, value: { data: '3', operator: OPERATOR_NOT } },
|
||||
{ type: TOKEN_TYPE_CONTACT, value: { data: '123', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_ORGANIZATION, value: { data: '456', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_HEALTH, value: { data: 'atRisk', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_HEALTH, value: { data: 'onTrack', operator: OPERATOR_NOT } },
|
||||
{ type: FILTERED_SEARCH_TERM, value: { data: 'find' } },
|
||||
{ type: FILTERED_SEARCH_TERM, value: { data: 'issues' } },
|
||||
];
|
||||
|
@ -239,6 +245,7 @@ export const filteredTokensWithSpecialValues = [
|
|||
{ type: TOKEN_TYPE_MILESTONE, value: { data: 'Upcoming', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_EPIC, value: { data: 'None', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_WEIGHT, value: { data: 'None', operator: OPERATOR_IS } },
|
||||
{ type: TOKEN_TYPE_HEALTH, value: { data: 'None', operator: OPERATOR_IS } },
|
||||
];
|
||||
|
||||
export const apiParams = {
|
||||
|
@ -255,6 +262,7 @@ export const apiParams = {
|
|||
weight: '1',
|
||||
crmContactId: '123',
|
||||
crmOrganizationId: '456',
|
||||
healthStatusFilter: 'atRisk',
|
||||
not: {
|
||||
authorUsername: 'marge',
|
||||
assigneeUsernames: ['patty', 'selma'],
|
||||
|
@ -266,6 +274,7 @@ export const apiParams = {
|
|||
iterationId: ['20', '42'],
|
||||
epicId: '34',
|
||||
weight: '3',
|
||||
healthStatusFilter: 'onTrack',
|
||||
},
|
||||
or: {
|
||||
authorUsernames: ['burns', 'smithers'],
|
||||
|
@ -283,6 +292,7 @@ export const apiParamsWithSpecialValues = {
|
|||
milestoneWildcardId: 'UPCOMING',
|
||||
epicId: 'None',
|
||||
weight: 'None',
|
||||
healthStatusFilter: 'NONE',
|
||||
};
|
||||
|
||||
export const urlParams = {
|
||||
|
@ -311,6 +321,8 @@ export const urlParams = {
|
|||
'not[weight]': '3',
|
||||
crm_contact_id: '123',
|
||||
crm_organization_id: '456',
|
||||
health_status: 'atRisk',
|
||||
'not[health_status]': 'onTrack',
|
||||
};
|
||||
|
||||
export const urlParamsWithSpecialValues = {
|
||||
|
@ -323,6 +335,7 @@ export const urlParamsWithSpecialValues = {
|
|||
milestone_title: 'Upcoming',
|
||||
epic_id: 'None',
|
||||
weight: 'None',
|
||||
health_status: 'None',
|
||||
};
|
||||
|
||||
export const project1 = {
|
||||
|
|
|
@ -38,7 +38,6 @@ describe('AccessRequestActionButtons', () => {
|
|||
title: 'Deny access',
|
||||
isAccessRequest: true,
|
||||
isInvite: false,
|
||||
icon: 'close',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -44,7 +44,6 @@ describe('InviteActionButtons', () => {
|
|||
title: 'Revoke invite',
|
||||
isAccessRequest: false,
|
||||
isInvite: true,
|
||||
icon: 'remove',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -79,18 +79,4 @@ describe('RemoveMemberButton', () => {
|
|||
|
||||
expect(actions.showRemoveMemberModal).toHaveBeenCalledWith(expect.any(Object), modalData);
|
||||
});
|
||||
|
||||
describe('button optional properties', () => {
|
||||
it('has default value for category and text', () => {
|
||||
createComponent();
|
||||
expect(findButton().props('category')).toBe('secondary');
|
||||
expect(findButton().text()).toBe('');
|
||||
});
|
||||
|
||||
it('allow changing value of button category and text', () => {
|
||||
createComponent({ buttonCategory: 'primary', buttonText: 'Decline request' });
|
||||
expect(findButton().props('category')).toBe('primary');
|
||||
expect(findButton().text()).toBe('Decline request');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,12 +43,9 @@ describe('UserActionButtons', () => {
|
|||
memberId: member.id,
|
||||
memberType: 'GroupMember',
|
||||
message: `Are you sure you want to remove ${member.user.name} from "${member.source.fullName}"?`,
|
||||
title: null,
|
||||
title: UserActionButtons.i18n.title,
|
||||
isAccessRequest: false,
|
||||
isInvite: false,
|
||||
icon: '',
|
||||
buttonCategory: 'secondary',
|
||||
buttonText: 'Remove member',
|
||||
userDeletionObstacles: {
|
||||
name: member.user.name,
|
||||
obstacles: parseUserDeletionObstacles(member.user),
|
||||
|
@ -132,30 +129,4 @@ describe('UserActionButtons', () => {
|
|||
expect(findRemoveMemberButton().props().memberType).toBe('ProjectMember');
|
||||
});
|
||||
});
|
||||
|
||||
describe('isInvitedUser', () => {
|
||||
it.each`
|
||||
isInvitedUser | icon | buttonText | buttonCategory
|
||||
${true} | ${'remove'} | ${null} | ${'primary'}
|
||||
${false} | ${''} | ${'Remove member'} | ${'secondary'}
|
||||
`(
|
||||
'passes the correct props to remove-member-button when isInvitedUser is $isInvitedUser',
|
||||
({ isInvitedUser, icon, buttonText, buttonCategory }) => {
|
||||
createComponent({
|
||||
isInvitedUser,
|
||||
permissions: {
|
||||
canRemove: true,
|
||||
},
|
||||
});
|
||||
|
||||
expect(findRemoveMemberButton().props()).toEqual(
|
||||
expect.objectContaining({
|
||||
icon,
|
||||
buttonText,
|
||||
buttonCategory,
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`MemberActivity with a member that does not have all of the fields renders \`User created\` field 1`] = `
|
||||
<div>
|
||||
<!---->
|
||||
|
||||
<div>
|
||||
<strong>
|
||||
Access granted:
|
||||
</strong>
|
||||
|
||||
<span>
|
||||
|
||||
Aug 06, 2020
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`MemberActivity with a member that has all fields renders \`User created\`, \`Access granted\`, and \`Last activity\` fields 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<strong>
|
||||
User created:
|
||||
</strong>
|
||||
|
||||
<span>
|
||||
|
||||
Mar 10, 2022
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>
|
||||
Access granted:
|
||||
</strong>
|
||||
|
||||
<span>
|
||||
|
||||
Jul 17, 2020
|
||||
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>
|
||||
Last activity:
|
||||
</strong>
|
||||
|
||||
<span>
|
||||
|
||||
Mar 15, 2022
|
||||
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
|
@ -1,20 +1,18 @@
|
|||
import { within } from '@testing-library/dom';
|
||||
import { mount, createWrapper } from '@vue/test-utils';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { useFakeDate } from 'helpers/fake_date';
|
||||
import CreatedAt from '~/members/components/table/created_at.vue';
|
||||
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
|
||||
|
||||
describe('CreatedAt', () => {
|
||||
// March 15th, 2020
|
||||
useFakeDate(2020, 2, 15);
|
||||
|
||||
const date = '2020-03-01T00:00:00.000';
|
||||
const dateTimeAgo = '2 weeks ago';
|
||||
const formattedDate = 'Mar 01, 2020';
|
||||
|
||||
let wrapper;
|
||||
|
||||
const createComponent = (propsData) => {
|
||||
wrapper = mount(CreatedAt, {
|
||||
wrapper = mountExtended(CreatedAt, {
|
||||
propsData: {
|
||||
date,
|
||||
...propsData,
|
||||
|
@ -22,9 +20,6 @@ describe('CreatedAt', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const getByText = (text, options) =>
|
||||
createWrapper(within(wrapper.element).getByText(text, options));
|
||||
|
||||
afterEach(() => {
|
||||
wrapper.destroy();
|
||||
});
|
||||
|
@ -35,11 +30,7 @@ describe('CreatedAt', () => {
|
|||
});
|
||||
|
||||
it('displays created at text', () => {
|
||||
expect(getByText(dateTimeAgo).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('uses `TimeAgoTooltip` component to display tooltip', () => {
|
||||
expect(wrapper.findComponent(TimeAgoTooltip).exists()).toBe(true);
|
||||
expect(wrapper.findByText(formattedDate).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -52,7 +43,7 @@ describe('CreatedAt', () => {
|
|||
},
|
||||
});
|
||||
|
||||
const link = getByText('Administrator');
|
||||
const link = wrapper.findByRole('link', { name: 'Administrator' });
|
||||
|
||||
expect(link.exists()).toBe(true);
|
||||
expect(link.attributes('href')).toBe('https://gitlab.com/root');
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import MemberActivity from '~/members/components/table/member_activity.vue';
|
||||
import { member as memberMock, group as groupLinkMock } from '../../mock_data';
|
||||
|
||||
describe('MemberActivity', () => {
|
||||
let wrapper;
|
||||
|
||||
const defaultPropsData = {
|
||||
member: memberMock,
|
||||
};
|
||||
|
||||
const createComponent = ({ propsData = {} } = {}) => {
|
||||
wrapper = mountExtended(MemberActivity, {
|
||||
propsData: {
|
||||
...defaultPropsData,
|
||||
...propsData,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
describe('with a member that has all fields', () => {
|
||||
beforeEach(() => {
|
||||
createComponent();
|
||||
});
|
||||
|
||||
it('renders `User created`, `Access granted`, and `Last activity` fields', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a member that does not have all of the fields', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({ propsData: { member: groupLinkMock } });
|
||||
});
|
||||
|
||||
it('renders `User created` field', () => {
|
||||
expect(wrapper.element).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,19 +1,25 @@
|
|||
import { getByText as getByTextHelper } from '@testing-library/dom';
|
||||
import { mount, createWrapper } from '@vue/test-utils';
|
||||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import MemberSource from '~/members/components/table/member_source.vue';
|
||||
|
||||
describe('MemberSource', () => {
|
||||
let wrapper;
|
||||
|
||||
const memberSource = {
|
||||
id: 102,
|
||||
fullName: 'Foo bar',
|
||||
webUrl: 'https://gitlab.com/groups/foo-bar',
|
||||
};
|
||||
|
||||
const createdBy = {
|
||||
name: 'Administrator',
|
||||
webUrl: 'https://gitlab.com/root',
|
||||
};
|
||||
|
||||
const createComponent = (propsData) => {
|
||||
wrapper = mount(MemberSource, {
|
||||
wrapper = mountExtended(MemberSource, {
|
||||
propsData: {
|
||||
memberSource: {
|
||||
id: 102,
|
||||
fullName: 'Foo bar',
|
||||
webUrl: 'https://gitlab.com/groups/foo-bar',
|
||||
},
|
||||
memberSource,
|
||||
...propsData,
|
||||
},
|
||||
directives: {
|
||||
|
@ -22,9 +28,6 @@ describe('MemberSource', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const getByText = (text, options) =>
|
||||
createWrapper(getByTextHelper(wrapper.element, text, options));
|
||||
|
||||
const getTooltipDirective = (elementWrapper) => getBinding(elementWrapper.element, 'gl-tooltip');
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -32,40 +35,69 @@ describe('MemberSource', () => {
|
|||
});
|
||||
|
||||
describe('direct member', () => {
|
||||
it('displays "Direct member"', () => {
|
||||
createComponent({
|
||||
isDirectMember: true,
|
||||
});
|
||||
describe('when created by is available', () => {
|
||||
it('displays "Direct member by <user name>"', () => {
|
||||
createComponent({
|
||||
isDirectMember: true,
|
||||
createdBy,
|
||||
});
|
||||
|
||||
expect(getByText('Direct member').exists()).toBe(true);
|
||||
expect(wrapper.text()).toBe('Direct member by Administrator');
|
||||
expect(wrapper.findByRole('link', { name: createdBy.name }).attributes('href')).toBe(
|
||||
createdBy.webUrl,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when created by is not available', () => {
|
||||
it('displays "Direct member"', () => {
|
||||
createComponent({
|
||||
isDirectMember: true,
|
||||
});
|
||||
|
||||
expect(wrapper.text()).toBe('Direct member');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('inherited member', () => {
|
||||
let sourceGroupLink;
|
||||
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
isDirectMember: false,
|
||||
describe('when created by is available', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
isDirectMember: false,
|
||||
createdBy,
|
||||
});
|
||||
});
|
||||
|
||||
sourceGroupLink = getByText('Foo bar');
|
||||
it('displays "<group name> by <user name>"', () => {
|
||||
expect(wrapper.text()).toBe('Foo bar by Administrator');
|
||||
expect(wrapper.findByRole('link', { name: memberSource.fullName }).attributes('href')).toBe(
|
||||
memberSource.webUrl,
|
||||
);
|
||||
expect(wrapper.findByRole('link', { name: createdBy.name }).attributes('href')).toBe(
|
||||
createdBy.webUrl,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('displays a link to source group', () => {
|
||||
createComponent({
|
||||
isDirectMember: false,
|
||||
describe('when created by is not available', () => {
|
||||
beforeEach(() => {
|
||||
createComponent({
|
||||
isDirectMember: false,
|
||||
});
|
||||
});
|
||||
|
||||
expect(sourceGroupLink.exists()).toBe(true);
|
||||
expect(sourceGroupLink.attributes('href')).toBe('https://gitlab.com/groups/foo-bar');
|
||||
});
|
||||
it('displays a link to source group', () => {
|
||||
expect(wrapper.text()).toBe(memberSource.fullName);
|
||||
expect(wrapper.attributes('href')).toBe(memberSource.webUrl);
|
||||
});
|
||||
|
||||
it('displays tooltip with "Inherited"', () => {
|
||||
const tooltipDirective = getTooltipDirective(sourceGroupLink);
|
||||
it('displays tooltip with "Inherited"', () => {
|
||||
const tooltipDirective = getTooltipDirective(wrapper);
|
||||
|
||||
expect(tooltipDirective).not.toBeUndefined();
|
||||
expect(sourceGroupLink.attributes('title')).toBe('Inherited');
|
||||
expect(tooltipDirective).not.toBeUndefined();
|
||||
expect(tooltipDirective.value).toBe('Inherited');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,9 +8,9 @@ import ExpirationDatepicker from '~/members/components/table/expiration_datepick
|
|||
import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
|
||||
import MemberAvatar from '~/members/components/table/member_avatar.vue';
|
||||
import MemberSource from '~/members/components/table/member_source.vue';
|
||||
import MemberActivity from '~/members/components/table/member_activity.vue';
|
||||
import MembersTable from '~/members/components/table/members_table.vue';
|
||||
import RoleDropdown from '~/members/components/table/role_dropdown.vue';
|
||||
import UserDate from '~/vue_shared/components/user_date.vue';
|
||||
import {
|
||||
MEMBER_TYPES,
|
||||
MEMBER_STATE_CREATED,
|
||||
|
@ -106,16 +106,14 @@ describe('MembersTable', () => {
|
|||
};
|
||||
|
||||
it.each`
|
||||
field | label | member | expectedComponent
|
||||
${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar}
|
||||
${'source'} | ${'Source'} | ${memberMock} | ${MemberSource}
|
||||
${'granted'} | ${'Access granted'} | ${memberMock} | ${CreatedAt}
|
||||
${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt}
|
||||
${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt}
|
||||
${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown}
|
||||
${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker}
|
||||
${'userCreatedAt'} | ${'Created on'} | ${memberMock} | ${UserDate}
|
||||
${'lastActivityOn'} | ${'Last activity'} | ${memberMock} | ${UserDate}
|
||||
field | label | member | expectedComponent
|
||||
${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar}
|
||||
${'source'} | ${'Source'} | ${memberMock} | ${MemberSource}
|
||||
${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt}
|
||||
${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt}
|
||||
${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown}
|
||||
${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker}
|
||||
${'activity'} | ${'Activity'} | ${memberMock} | ${MemberActivity}
|
||||
`('renders the $label field', ({ field, label, member, expectedComponent }) => {
|
||||
createComponent({
|
||||
members: [member],
|
||||
|
|
|
@ -8,13 +8,21 @@ RSpec.shared_examples 'validate path globs' do |path_globs|
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'validate schema data' do |tables_and_views|
|
||||
it 'all tables and views have assigned a known gitlab_schema' do
|
||||
expect(tables_and_views).to all(
|
||||
match([be_a(String), be_in(Gitlab::Database.schemas_to_base_models.keys.map(&:to_sym))])
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
RSpec.describe Gitlab::Database::GitlabSchema do
|
||||
describe '.deleted_views_and_tables_to_schema' do
|
||||
include_examples 'validate schema data', described_class.deleted_views_and_tables_to_schema
|
||||
end
|
||||
|
||||
describe '.views_and_tables_to_schema' do
|
||||
it 'all tables and views have assigned a known gitlab_schema' do
|
||||
expect(described_class.views_and_tables_to_schema).to all(
|
||||
match([be_a(String), be_in(Gitlab::Database.schemas_to_base_models.keys.map(&:to_sym))])
|
||||
)
|
||||
end
|
||||
include_examples 'validate schema data', described_class.views_and_tables_to_schema
|
||||
|
||||
# This being run across different databases indirectly also tests
|
||||
# a general consistency of structure across databases
|
||||
|
@ -55,6 +63,14 @@ RSpec.describe Gitlab::Database::GitlabSchema do
|
|||
include_examples 'validate path globs', described_class.view_path_globs
|
||||
end
|
||||
|
||||
describe '.deleted_tables_path_globs' do
|
||||
include_examples 'validate path globs', described_class.deleted_tables_path_globs
|
||||
end
|
||||
|
||||
describe '.deleted_views_path_globs' do
|
||||
include_examples 'validate path globs', described_class.deleted_views_path_globs
|
||||
end
|
||||
|
||||
describe '.tables_to_schema' do
|
||||
let(:database_models) { Gitlab::Database.database_base_models.except(:geo) }
|
||||
let(:views) { database_models.flat_map { |_, m| m.connection.views }.sort.uniq }
|
||||
|
|
|
@ -95,7 +95,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers::AutomaticLockWritesOnTables,
|
|||
|
||||
context 'when table listed as a deleted table' do
|
||||
before do
|
||||
stub_const("Gitlab::Database::GitlabSchema::DELETED_TABLES", { table_name.to_s => :gitlab_main })
|
||||
allow(Gitlab::Database::GitlabSchema).to receive(:deleted_tables_to_schema).and_return(
|
||||
{ table_name.to_s => :gitlab_main }
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci]
|
||||
|
@ -132,7 +134,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers::AutomaticLockWritesOnTables,
|
|||
|
||||
context 'when table listed as a deleted table' do
|
||||
before do
|
||||
stub_const("Gitlab::Database::GitlabSchema::DELETED_TABLES", { table_name.to_s => :gitlab_ci })
|
||||
allow(Gitlab::Database::GitlabSchema).to receive(:deleted_tables_to_schema).and_return(
|
||||
{ table_name.to_s => :gitlab_ci }
|
||||
)
|
||||
end
|
||||
|
||||
it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main]
|
||||
|
|
|
@ -11,9 +11,26 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
let_it_be(:entity_3) { create(:bulk_import_entity, bulk_import: import_2) }
|
||||
let_it_be(:failure_3) { create(:bulk_import_failure, entity: entity_3) }
|
||||
|
||||
before do
|
||||
stub_application_setting(bulk_import_enabled: true)
|
||||
end
|
||||
|
||||
shared_examples 'disabled feature' do
|
||||
it 'returns 404' do
|
||||
stub_application_setting(bulk_import_enabled: false)
|
||||
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /bulk_imports' do
|
||||
let(:request) { get api('/bulk_imports', user), params: params }
|
||||
let(:params) { {} }
|
||||
|
||||
it 'returns a list of bulk imports authored by the user' do
|
||||
get api('/bulk_imports', user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to contain_exactly(import_1.id, import_2.id)
|
||||
|
@ -21,26 +38,38 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
|
||||
context 'sort parameter' do
|
||||
it 'sorts by created_at descending by default' do
|
||||
get api('/bulk_imports', user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to eq([import_2.id, import_1.id])
|
||||
end
|
||||
|
||||
it 'sorts by created_at descending when explicitly specified' do
|
||||
get api('/bulk_imports', user), params: { sort: 'desc' }
|
||||
context 'when explicitly specified' do
|
||||
context 'when descending' do
|
||||
let(:params) { { sort: 'desc' } }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to eq([import_2.id, import_1.id])
|
||||
end
|
||||
it 'sorts by created_at descending' do
|
||||
request
|
||||
|
||||
it 'sorts by created_at ascending when explicitly specified' do
|
||||
get api('/bulk_imports', user), params: { sort: 'asc' }
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to match_array([import_2.id, import_1.id])
|
||||
end
|
||||
end
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to eq([import_1.id, import_2.id])
|
||||
context 'when ascending' do
|
||||
let(:params) { { sort: 'asc' } }
|
||||
|
||||
it 'sorts by created_at ascending when explicitly specified' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to match_array([import_1.id, import_2.id])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'disabled feature'
|
||||
end
|
||||
|
||||
describe 'POST /bulk_imports' do
|
||||
|
@ -56,21 +85,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when bulk_import feature flag is disabled' do
|
||||
before do
|
||||
stub_feature_flags(bulk_import: false)
|
||||
end
|
||||
|
||||
it 'returns 404' do
|
||||
post api('/bulk_imports', user), params: {}
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'starting a new migration' do
|
||||
it 'starts a new migration' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
let(:request) { post api('/bulk_imports', user), params: params }
|
||||
let(:params) do
|
||||
{
|
||||
configuration: {
|
||||
url: 'http://gitlab.example',
|
||||
access_token: 'access_token'
|
||||
|
@ -83,6 +101,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
}.merge(destination_param)
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'starts a new migration' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:created)
|
||||
|
||||
|
@ -99,8 +121,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
end
|
||||
|
||||
context 'when both destination_name & destination_slug are provided' do
|
||||
it 'returns a mutually exclusive error' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
let(:params) do
|
||||
{
|
||||
configuration: {
|
||||
url: 'http://gitlab.example',
|
||||
access_token: 'access_token'
|
||||
|
@ -115,6 +137,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns a mutually exclusive error' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
|
||||
|
@ -123,8 +149,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
end
|
||||
|
||||
context 'when neither destination_name nor destination_slug is provided' do
|
||||
it 'returns at_least_one_of error' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
let(:params) do
|
||||
{
|
||||
configuration: {
|
||||
url: 'http://gitlab.example',
|
||||
access_token: 'access_token'
|
||||
|
@ -137,6 +163,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns at_least_one_of error' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
|
||||
|
@ -145,8 +175,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
end
|
||||
|
||||
context 'when provided url is blocked' do
|
||||
it 'returns blocked url error' do
|
||||
post api('/bulk_imports', user), params: {
|
||||
let(:params) do
|
||||
{
|
||||
configuration: {
|
||||
url: 'url',
|
||||
access_token: 'access_token'
|
||||
|
@ -158,49 +188,71 @@ RSpec.describe API::BulkImports, feature_category: :importers do
|
|||
destination_namespace: 'destination_namespace'
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns blocked url error' do
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:unprocessable_entity)
|
||||
|
||||
expect(json_response['message']).to eq('Validation failed: Url is blocked: Only allowed schemes are http, https')
|
||||
end
|
||||
end
|
||||
|
||||
include_examples 'disabled feature'
|
||||
end
|
||||
|
||||
describe 'GET /bulk_imports/entities' do
|
||||
let(:request) { get api('/bulk_imports/entities', user) }
|
||||
|
||||
it 'returns a list of all import entities authored by the user' do
|
||||
get api('/bulk_imports/entities', user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to contain_exactly(entity_1.id, entity_2.id, entity_3.id)
|
||||
end
|
||||
|
||||
include_examples 'disabled feature'
|
||||
end
|
||||
|
||||
describe 'GET /bulk_imports/:id' do
|
||||
let(:request) { get api("/bulk_imports/#{import_1.id}", user) }
|
||||
|
||||
it 'returns specified bulk import' do
|
||||
get api("/bulk_imports/#{import_1.id}", user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['id']).to eq(import_1.id)
|
||||
end
|
||||
|
||||
include_examples 'disabled feature'
|
||||
end
|
||||
|
||||
describe 'GET /bulk_imports/:id/entities' do
|
||||
let(:request) { get api("/bulk_imports/#{import_2.id}/entities", user) }
|
||||
|
||||
it 'returns specified bulk import entities with failures' do
|
||||
get api("/bulk_imports/#{import_2.id}/entities", user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response.pluck('id')).to contain_exactly(entity_3.id)
|
||||
expect(json_response.first['failures'].first['exception_class']).to eq(failure_3.exception_class)
|
||||
end
|
||||
|
||||
include_examples 'disabled feature'
|
||||
end
|
||||
|
||||
describe 'GET /bulk_imports/:id/entities/:entity_id' do
|
||||
let(:request) { get api("/bulk_imports/#{import_1.id}/entities/#{entity_2.id}", user) }
|
||||
|
||||
it 'returns specified bulk import entity' do
|
||||
get api("/bulk_imports/#{import_1.id}/entities/#{entity_2.id}", user)
|
||||
request
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response['id']).to eq(entity_2.id)
|
||||
end
|
||||
|
||||
include_examples 'disabled feature'
|
||||
end
|
||||
|
||||
context 'when user is unauthenticated' do
|
||||
|
|
|
@ -173,6 +173,8 @@ RSpec.describe API::GroupExport, feature_category: :importers do
|
|||
let(:status_path) { "/groups/#{group.id}/export_relations/status" }
|
||||
|
||||
before do
|
||||
stub_application_setting(bulk_import_enabled: true)
|
||||
|
||||
group.add_owner(user)
|
||||
end
|
||||
|
||||
|
@ -212,11 +214,12 @@ RSpec.describe API::GroupExport, feature_category: :importers do
|
|||
|
||||
context 'when export_file.file does not exist' do
|
||||
it 'returns 404' do
|
||||
allow(upload).to receive(:export_file).and_return(nil)
|
||||
allow(export).to receive(:upload).and_return(nil)
|
||||
|
||||
get api(download_path, user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
expect(json_response['message']).to eq('404 Not found')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -234,5 +237,11 @@ RSpec.describe API::GroupExport, feature_category: :importers do
|
|||
expect(json_response.pluck('status')).to contain_exactly(-1, 0, 1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when bulk import is disabled' do
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { get api(path, user) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -511,6 +511,10 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category:
|
|||
|
||||
let_it_be(:status_path) { "/projects/#{project.id}/export_relations/status" }
|
||||
|
||||
before do
|
||||
stub_application_setting(bulk_import_enabled: true)
|
||||
end
|
||||
|
||||
context 'when user is a maintainer' do
|
||||
before do
|
||||
project.add_maintainer(user)
|
||||
|
@ -584,9 +588,9 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category:
|
|||
end
|
||||
end
|
||||
|
||||
context 'with bulk_import FF disabled' do
|
||||
context 'with bulk_import is disabled' do
|
||||
before do
|
||||
stub_feature_flags(bulk_import: false)
|
||||
stub_application_setting(bulk_import_enabled: false)
|
||||
end
|
||||
|
||||
describe 'POST /projects/:id/export_relations' do
|
||||
|
@ -641,5 +645,11 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category:
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when bulk import is disabled' do
|
||||
it_behaves_like '404 response' do
|
||||
let(:request) { get api(path, user) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
172
yarn.lock
172
yarn.lock
|
@ -1627,6 +1627,19 @@
|
|||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
|
||||
integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==
|
||||
|
||||
"@mermaid-js/mermaid-mindmap@^9.3.0":
|
||||
version "9.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@mermaid-js/mermaid-mindmap/-/mermaid-mindmap-9.3.0.tgz#cfe10329198a0f37e27eef1dcc4a1cf21f187e2b"
|
||||
integrity sha512-IhtYSVBBRYviH1Ehu8gk69pMDF8DSRqXBRDMWrEfHoaMruHeaP2DXA3PBnuwsMaCdPQhlUUcy/7DBLAEIXvCAw==
|
||||
dependencies:
|
||||
"@braintree/sanitize-url" "^6.0.0"
|
||||
cytoscape "^3.23.0"
|
||||
cytoscape-cose-bilkent "^4.1.0"
|
||||
cytoscape-fcose "^2.1.0"
|
||||
d3 "^7.0.0"
|
||||
khroma "^2.0.0"
|
||||
non-layered-tidy-tree-layout "^2.0.2"
|
||||
|
||||
"@miragejs/pretender-node-polyfill@^0.1.0":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@miragejs/pretender-node-polyfill/-/pretender-node-polyfill-0.1.2.tgz#d26b6b7483fb70cd62189d05c95d2f67153e43f2"
|
||||
|
@ -4007,6 +4020,20 @@ core-util-is@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
|
||||
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
|
||||
|
||||
cose-base@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-1.0.3.tgz#650334b41b869578a543358b80cda7e0abe0a60a"
|
||||
integrity sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==
|
||||
dependencies:
|
||||
layout-base "^1.0.0"
|
||||
|
||||
cose-base@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cose-base/-/cose-base-2.1.0.tgz#89b2d4a59d7bd0cde3138a4689825f3e8a5abd6a"
|
||||
integrity sha512-HTMm07dhxq1dIPGWwpiVrIk9n+DH7KYmqWA786mLe8jDS+1ZjGtJGIIsJVKoseZXS6/FxiUWCJ2B7XzqUCuhPw==
|
||||
dependencies:
|
||||
layout-base "^2.0.0"
|
||||
|
||||
cosmiconfig-toml-loader@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cosmiconfig-toml-loader/-/cosmiconfig-toml-loader-1.0.0.tgz#0681383651cceff918177debe9084c0d3769509b"
|
||||
|
@ -4245,15 +4272,37 @@ cyclist@~0.2.2:
|
|||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
|
||||
integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
|
||||
|
||||
cytoscape-cose-bilkent@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz#762fa121df9930ffeb51a495d87917c570ac209b"
|
||||
integrity sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==
|
||||
dependencies:
|
||||
cose-base "^1.0.0"
|
||||
|
||||
cytoscape-fcose@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/cytoscape-fcose/-/cytoscape-fcose-2.1.0.tgz#04c3093776ea6b71787009de641607db7d4edf55"
|
||||
integrity sha512-Q3apPl66jf8/2sMsrCjNP247nbDkyIPjA9g5iPMMWNLZgP3/mn9aryF7EFY/oRPEpv7bKJ4jYmCoU5r5/qAc1Q==
|
||||
dependencies:
|
||||
cose-base "^2.0.0"
|
||||
|
||||
cytoscape@^3.23.0:
|
||||
version "3.23.0"
|
||||
resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.23.0.tgz#054ee05a6d0aa3b4f139382bbf2f4e5226df3c6d"
|
||||
integrity sha512-gRZqJj/1kiAVPkrVFvz/GccxsXhF3Qwpptl32gKKypO4IlqnKBjTOu+HbXtEggSGzC5KCaHp3/F7GgENrtsFkA==
|
||||
dependencies:
|
||||
heap "^0.2.6"
|
||||
lodash "^4.17.21"
|
||||
|
||||
d3-array@1, "d3-array@1 - 2", d3-array@^1.1.1, d3-array@^1.2.0:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
|
||||
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.0.4.tgz#60550bcc9818be9ace88d269ccd97038fc399b55"
|
||||
integrity sha512-ShFl90cxNqDaSynDF/Bik/kTzISqePqU3qo2fv6kSJEvF7y7tDCDpcU6WiT01rPO6zngZnrvJ/0j4q6Qg+5EQg==
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3, d3-array@^3.2.0:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.1.tgz#39331ea706f5709417d31bbb6ec152e0328b39b3"
|
||||
integrity sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
|
@ -4326,12 +4375,12 @@ d3-contour@1:
|
|||
dependencies:
|
||||
d3-array "^1.1.1"
|
||||
|
||||
d3-contour@3:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-3.0.1.tgz#2c64255d43059599cd0dba8fe4cc3d51ccdd9bbd"
|
||||
integrity sha512-0Oc4D0KyhwhM7ZL0RMnfGycLN7hxHB8CMmwZ3+H26PWAG0ozNuYG5hXSDNgmP1SgJkQMrlG6cP20HoaSbvcJTQ==
|
||||
d3-contour@4:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.0.tgz#5a1337c6da0d528479acdb5db54bc81a0ff2ec6b"
|
||||
integrity sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==
|
||||
dependencies:
|
||||
d3-array "2 - 3"
|
||||
d3-array "^3.2.0"
|
||||
|
||||
d3-delaunay@6:
|
||||
version "6.0.2"
|
||||
|
@ -4672,7 +4721,7 @@ d3-zoom@3:
|
|||
d3-selection "2 - 3"
|
||||
d3-transition "2 - 3"
|
||||
|
||||
d3@^5.14, d3@^5.16.0:
|
||||
d3@^5.16.0:
|
||||
version "5.16.0"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877"
|
||||
integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==
|
||||
|
@ -4709,17 +4758,17 @@ d3@^5.14, d3@^5.16.0:
|
|||
d3-voronoi "1"
|
||||
d3-zoom "1"
|
||||
|
||||
d3@^7.0.0:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-7.0.4.tgz#37dfeb3b526f64a0de2ddb705ea61649325207bd"
|
||||
integrity sha512-ruRiyPYZEGeJBOOjVS5pHliNUZM2HAllEY7HKB2ff+9ENxOti4N+S+WZqo9ggUMr8tSPMm+riqKpJd1oYEDN5Q==
|
||||
d3@^7.0.0, d3@^7.7.0:
|
||||
version "7.7.0"
|
||||
resolved "https://registry.yarnpkg.com/d3/-/d3-7.7.0.tgz#e7779a74ea7c807b432fdfd8128de062b19c62eb"
|
||||
integrity sha512-VEwHCMgMjD2WBsxeRGUE18RmzxT9Bn7ghDpzvTEvkLSBAKgTMydJjouZTjspgQfRHpPt/PB3EHWBa6SSyFQq4g==
|
||||
dependencies:
|
||||
d3-array "3"
|
||||
d3-axis "3"
|
||||
d3-brush "3"
|
||||
d3-chord "3"
|
||||
d3-color "3"
|
||||
d3-contour "3"
|
||||
d3-contour "4"
|
||||
d3-delaunay "6"
|
||||
d3-dispatch "3"
|
||||
d3-drag "3"
|
||||
|
@ -4745,23 +4794,13 @@ d3@^7.0.0:
|
|||
d3-transition "3"
|
||||
d3-zoom "3"
|
||||
|
||||
dagre-d3@^0.6.4:
|
||||
version "0.6.4"
|
||||
resolved "https://registry.yarnpkg.com/dagre-d3/-/dagre-d3-0.6.4.tgz#0728d5ce7f177ca2337df141ceb60fbe6eeb7b29"
|
||||
integrity sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ==
|
||||
dagre-d3-es@7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.6.tgz#8cab465ff95aca8a1ca2292d07e1fb31b5db83f2"
|
||||
integrity sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==
|
||||
dependencies:
|
||||
d3 "^5.14"
|
||||
dagre "^0.8.5"
|
||||
graphlib "^2.1.8"
|
||||
lodash "^4.17.15"
|
||||
|
||||
dagre@^0.8.5:
|
||||
version "0.8.5"
|
||||
resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee"
|
||||
integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==
|
||||
dependencies:
|
||||
graphlib "^2.1.8"
|
||||
lodash "^4.17.15"
|
||||
d3 "^7.7.0"
|
||||
lodash-es "^4.17.21"
|
||||
|
||||
data-urls@^3.0.1:
|
||||
version "3.0.2"
|
||||
|
@ -5090,12 +5129,7 @@ dommatrix@^1.0.3:
|
|||
resolved "https://registry.yarnpkg.com/dommatrix/-/dommatrix-1.0.3.tgz#e7c18e8d6f3abdd1fef3dd4aa74c4d2e620a0525"
|
||||
integrity sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==
|
||||
|
||||
dompurify@2.3.8:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f"
|
||||
integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw==
|
||||
|
||||
dompurify@^2.4.1:
|
||||
dompurify@2.4.1, dompurify@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631"
|
||||
integrity sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==
|
||||
|
@ -6445,13 +6479,6 @@ grapheme-splitter@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
|
||||
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==
|
||||
|
||||
graphlib@^2.1.8:
|
||||
version "2.1.8"
|
||||
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
|
||||
integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
|
||||
dependencies:
|
||||
lodash "^4.17.15"
|
||||
|
||||
graphql-config@^4.3.6:
|
||||
version "4.3.6"
|
||||
resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-4.3.6.tgz#908ef03d6670c3068e51fe2e84e10e3e0af220b6"
|
||||
|
@ -6711,6 +6738,11 @@ he@^1.1.0:
|
|||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
heap@^0.2.6:
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.7.tgz#1e6adf711d3f27ce35a81fe3b7bd576c2260a8fc"
|
||||
integrity sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==
|
||||
|
||||
highlight.js@^11.5.1, highlight.js@~11.5.0:
|
||||
version "11.5.1"
|
||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.5.1.tgz#027c24e4509e2f4dcd00b4a6dda542ce0a1f7aea"
|
||||
|
@ -8055,6 +8087,16 @@ known-css-properties@^0.25.0:
|
|||
resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776"
|
||||
integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA==
|
||||
|
||||
layout-base@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-1.0.2.tgz#1291e296883c322a9dd4c5dd82063721b53e26e2"
|
||||
integrity sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==
|
||||
|
||||
layout-base@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/layout-base/-/layout-base-2.0.1.tgz#d0337913586c90f9c2c075292069f5c2da5dd285"
|
||||
integrity sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==
|
||||
|
||||
leven@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||
|
@ -8153,6 +8195,11 @@ locate-path@^6.0.0:
|
|||
dependencies:
|
||||
p-locate "^5.0.0"
|
||||
|
||||
lodash-es@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee"
|
||||
integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
|
||||
|
||||
lodash.assign@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
|
||||
|
@ -8674,20 +8721,21 @@ merge2@^1.3.0, merge2@^1.4.1:
|
|||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
mermaid@^9.1.3:
|
||||
version "9.1.3"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.1.3.tgz#15d08662c66250124ce31106a4620285061ac59c"
|
||||
integrity sha512-jTIYiqKwsUXVCoxHUVkK8t0QN3zSKIdJlb9thT0J5jCnzXyc+gqTbZE2QmjRfavFTPPn5eRy5zaFp7V+6RhxYg==
|
||||
mermaid@^9.3.0:
|
||||
version "9.3.0"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.3.0.tgz#8bd7c4a44b53e4e85c53a0a474442e9c273494ae"
|
||||
integrity sha512-mGl0BM19TD/HbU/LmlaZbjBi//tojelg8P/mxD6pPZTAYaI+VawcyBdqRsoUHSc7j71PrMdJ3HBadoQNdvP5cg==
|
||||
dependencies:
|
||||
"@braintree/sanitize-url" "^6.0.0"
|
||||
d3 "^7.0.0"
|
||||
dagre "^0.8.5"
|
||||
dagre-d3 "^0.6.4"
|
||||
dompurify "2.3.8"
|
||||
graphlib "^2.1.8"
|
||||
dagre-d3-es "7.0.6"
|
||||
dompurify "2.4.1"
|
||||
khroma "^2.0.0"
|
||||
lodash-es "^4.17.21"
|
||||
moment-mini "^2.24.0"
|
||||
stylis "^4.0.10"
|
||||
non-layered-tidy-tree-layout "^2.0.2"
|
||||
stylis "^4.1.2"
|
||||
uuid "^9.0.0"
|
||||
|
||||
meros@^1.1.4:
|
||||
version "1.2.0"
|
||||
|
@ -9428,6 +9476,11 @@ nomnom@^1.5.x:
|
|||
chalk "~0.4.0"
|
||||
underscore "~1.6.0"
|
||||
|
||||
non-layered-tidy-tree-layout@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
|
||||
integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
|
||||
|
||||
nopt@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48"
|
||||
|
@ -11558,10 +11611,10 @@ stylelint@^14.9.1:
|
|||
v8-compile-cache "^2.3.0"
|
||||
write-file-atomic "^4.0.1"
|
||||
|
||||
stylis@^4.0.10:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240"
|
||||
integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==
|
||||
stylis@^4.1.2:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7"
|
||||
integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
|
||||
|
||||
subscriptions-transport-ws@^0.11.0:
|
||||
version "0.11.0"
|
||||
|
@ -12287,6 +12340,11 @@ uuid@^8.3.2:
|
|||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||
|
||||
uuid@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
uvu@^0.5.0:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.3.tgz#3d83c5bc1230f153451877bfc7f4aea2392219ae"
|
||||
|
|
Loading…
Reference in New Issue