Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-12-21 09:07:17 +00:00
parent 4cd1329b80
commit 47a3dc6551
65 changed files with 960 additions and 553 deletions

View File

@ -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) { if (search) {
filteredSearchValue.push(search); filteredSearchValue.push(search);
} }
@ -285,6 +292,7 @@ export default {
'not[my_reaction_emoji]': this.filterParams.not.myReactionEmoji, 'not[my_reaction_emoji]': this.filterParams.not.myReactionEmoji,
'not[iteration_id]': this.filterParams.not.iterationId, 'not[iteration_id]': this.filterParams.not.iterationId,
'not[release_tag]': this.filterParams.not.releaseTag, 'not[release_tag]': this.filterParams.not.releaseTag,
'not[health_status]': this.filterParams.not.healthStatus,
}, },
undefined, undefined,
); );

View File

@ -360,14 +360,17 @@ export const filters = {
}, },
[TOKEN_TYPE_HEALTH]: { [TOKEN_TYPE_HEALTH]: {
[API_PARAM]: { [API_PARAM]: {
[NORMAL_FILTER]: 'healthStatus', [NORMAL_FILTER]: 'healthStatusFilter',
[SPECIAL_FILTER]: 'healthStatus', [SPECIAL_FILTER]: 'healthStatusFilter',
}, },
[URL_PARAM]: { [URL_PARAM]: {
[OPERATOR_IS]: { [OPERATOR_IS]: {
[NORMAL_FILTER]: 'health_status', [NORMAL_FILTER]: 'health_status',
[SPECIAL_FILTER]: 'health_status', [SPECIAL_FILTER]: 'health_status',
}, },
[OPERATOR_NOT]: {
[NORMAL_FILTER]: 'not[health_status]',
},
}, },
}, },
[TOKEN_TYPE_CONTACT]: { [TOKEN_TYPE_CONTACT]: {

View File

@ -13,6 +13,7 @@ import {
TOKEN_TYPE_MILESTONE, TOKEN_TYPE_MILESTONE,
TOKEN_TYPE_RELEASE, TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE, TOKEN_TYPE_TYPE,
TOKEN_TYPE_HEALTH,
} from '~/vue_shared/components/filtered_search_bar/constants'; } from '~/vue_shared/components/filtered_search_bar/constants';
import { import {
ALTERNATIVE_FILTER, ALTERNATIVE_FILTER,
@ -267,8 +268,13 @@ const wildcardTokens = [TOKEN_TYPE_ITERATION, TOKEN_TYPE_MILESTONE, TOKEN_TYPE_R
const isWildcardValue = (tokenType, value) => const isWildcardValue = (tokenType, value) =>
wildcardTokens.includes(tokenType) && specialFilterValues.includes(value); wildcardTokens.includes(tokenType) && specialFilterValues.includes(value);
const isHealthStatusSpecialFilter = (tokenType, value) =>
tokenType === TOKEN_TYPE_HEALTH && specialFilterValues.includes(value);
const requiresUpperCaseValue = (tokenType, 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) => { const formatData = (token) => {
if (requiresUpperCaseValue(token.type, token.value.data)) { if (requiresUpperCaseValue(token.type, token.value.data)) {

View File

@ -1,4 +1,5 @@
import mermaid from 'mermaid'; import mermaid from 'mermaid';
import mindmap from '@mermaid-js/mermaid-mindmap';
import { getParameterByName } from '~/lib/utils/url_utility'; import { getParameterByName } from '~/lib/utils/url_utility';
const setIframeRenderedSize = (h, w) => { const setIframeRenderedSize = (h, w) => {
@ -12,11 +13,10 @@ const drawDiagram = (source) => {
// eslint-disable-next-line no-unsanitized/property // eslint-disable-next-line no-unsanitized/property
element.innerHTML = svgCode; element.innerHTML = svgCode;
const height = parseInt(element.firstElementChild.getAttribute('height'), 10); const { width, height } = element.firstElementChild.viewBox.baseVal;
const width = parseInt(element.firstElementChild.style.maxWidth, 10);
setIframeRenderedSize(height, width); setIframeRenderedSize(height, width);
}; };
mermaid.mermaidAPI.render('mermaid', source, insertSvg); mermaid.mermaidAPI.renderAsync('mermaid', source, insertSvg);
}; };
const darkModeEnabled = () => getParameterByName('darkMode') === 'true'; const darkModeEnabled = () => getParameterByName('darkMode') === 'true';
@ -56,7 +56,13 @@ const addListener = () => {
false, false,
); );
}; };
mermaid
addListener(); .registerExternalDiagrams([mindmap])
initMermaid(); .then(() => {
addListener();
initMermaid();
})
.catch((error) => {
throw error;
});
export default {}; export default {};

View File

@ -49,8 +49,6 @@ export default {
:message="message" :message="message"
:title="s__('Member|Deny access')" :title="s__('Member|Deny access')"
:is-access-request="true" :is-access-request="true"
icon="close"
button-category="primary"
/> />
</div> </div>
</action-button-group> </action-button-group>

View File

@ -40,7 +40,6 @@ export default {
:title="$options.title" :title="$options.title"
:aria-label="$options.title" :aria-label="$options.title"
icon="check" icon="check"
variant="confirm"
type="submit" type="submit"
/> />
</gl-form> </gl-form>

View File

@ -41,8 +41,6 @@ export default {
<remove-member-button <remove-member-button
:member-id="member.id" :member-id="member.id"
:message="message" :message="message"
icon="remove"
button-category="primary"
:title="s__('Member|Revoke invite')" :title="s__('Member|Revoke invite')"
is-invite is-invite
/> />

View File

@ -33,7 +33,6 @@ export default {
:title="$options.title" :title="$options.title"
:aria-label="$options.title" :aria-label="$options.title"
icon="leave" icon="leave"
variant="danger"
/> />
<leave-modal :member="member" /> <leave-modal :member="member" />
</div> </div>

View File

@ -32,7 +32,6 @@ export default {
<template> <template>
<gl-button <gl-button
v-gl-tooltip.hover v-gl-tooltip.hover
variant="danger"
:title="$options.i18n.buttonTitle" :title="$options.i18n.buttonTitle"
:aria-label="$options.i18n.buttonTitle" :aria-label="$options.i18n.buttonTitle"
icon="remove" icon="remove"

View File

@ -25,23 +25,7 @@ export default {
}, },
title: { title: {
type: String, type: String,
required: false, required: true,
default: null,
},
icon: {
type: String,
required: false,
default: undefined,
},
buttonText: {
type: String,
required: false,
default: '',
},
buttonCategory: {
type: String,
required: false,
default: 'secondary',
}, },
isAccessRequest: { isAccessRequest: {
type: Boolean, type: Boolean,
@ -89,13 +73,10 @@ export default {
<template> <template>
<gl-button <gl-button
v-gl-tooltip v-gl-tooltip
variant="danger"
:category="buttonCategory"
:title="title" :title="title"
:aria-label="title" :aria-label="title"
:icon="icon" icon="remove"
data-qa-selector="delete_member_button" data-qa-selector="delete_member_button"
@click="showRemoveMemberModal(modalData)" @click="showRemoveMemberModal(modalData)"
><template v-if="buttonText">{{ buttonText }}</template></gl-button />
>
</template> </template>

View File

@ -1,5 +1,5 @@
<script> <script>
import { __, s__, sprintf } from '~/locale'; import { s__, __, sprintf } from '~/locale';
import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils'; import { parseUserDeletionObstacles } from '~/vue_shared/components/user_deletion_obstacles/utils';
import ActionButtonGroup from './action_button_group.vue'; import ActionButtonGroup from './action_button_group.vue';
import LeaveButton from './leave_button.vue'; import LeaveButton from './leave_button.vue';
@ -7,6 +7,9 @@ import RemoveMemberButton from './remove_member_button.vue';
export default { export default {
name: 'UserActionButtons', name: 'UserActionButtons',
i18n: {
title: __('Remove member'),
},
components: { components: {
ActionButtonGroup, ActionButtonGroup,
RemoveMemberButton, RemoveMemberButton,
@ -23,10 +26,6 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
isInvitedUser: {
type: Boolean,
required: true,
},
permissions: { permissions: {
type: Object, type: Object,
required: true, required: true,
@ -60,15 +59,6 @@ export default {
obstacles: parseUserDeletionObstacles(this.member.user), obstacles: parseUserDeletionObstacles(this.member.user),
}; };
}, },
removeMemberButtonText() {
return this.isInvitedUser ? null : __('Remove member');
},
removeMemberButtonIcon() {
return this.isInvitedUser ? 'remove' : '';
},
removeMemberButtonCategory() {
return this.isInvitedUser ? 'primary' : 'secondary';
},
}, },
}; };
</script> </script>
@ -83,9 +73,7 @@ export default {
:member-type="member.type" :member-type="member.type"
:user-deletion-obstacles="userDeletionObstaclesUserData" :user-deletion-obstacles="userDeletionObstaclesUserData"
:message="message" :message="message"
:icon="removeMemberButtonIcon" :title="$options.i18n.title"
:button-text="removeMemberButtonText"
:button-category="removeMemberButtonCategory"
/> />
</div> </div>
<div v-else-if="permissions.canOverride && !member.isOverridden" class="gl-px-1"> <div v-else-if="permissions.canOverride && !member.isOverridden" class="gl-px-1">

View File

@ -1,10 +1,10 @@
<script> <script>
import { GlSprintf } from '@gitlab/ui'; 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 { export default {
name: 'CreatedAt', name: 'CreatedAt',
components: { GlSprintf, TimeAgoTooltip }, components: { GlSprintf, UserDate },
props: { props: {
date: { date: {
type: String, type: String,
@ -29,12 +29,12 @@ export default {
<span> <span>
<gl-sprintf v-if="showCreatedBy" :message="s__('Members|%{time} by %{user}')"> <gl-sprintf v-if="showCreatedBy" :message="s__('Members|%{time} by %{user}')">
<template #time> <template #time>
<time-ago-tooltip :time="date" /> <user-date :date="date" />
</template> </template>
<template #user> <template #user>
<a :href="createdBy.webUrl">{{ createdBy.name }}</a> <a :href="createdBy.webUrl">{{ createdBy.name }}</a>
</template> </template>
</gl-sprintf> </gl-sprintf>
<time-ago-tooltip v-else :time="date" /> <user-date v-else :date="date" />
</span> </span>
</template> </template>

View File

@ -32,10 +32,6 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
isInvitedUser: {
type: Boolean,
required: true,
},
}, },
computed: { computed: {
actionButtonComponent() { actionButtonComponent() {
@ -60,6 +56,5 @@ export default {
:member="member" :member="member"
:permissions="permissions" :permissions="permissions"
:is-current-user="isCurrentUser" :is-current-user="isCurrentUser"
:is-invited-user="isInvitedUser"
/> />
</template> </template>

View File

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

View File

@ -1,11 +1,19 @@
<script> <script>
import { GlTooltipDirective } from '@gitlab/ui'; import { GlSprintf, GlTooltipDirective } from '@gitlab/ui';
import { s__, __ } from '~/locale';
export default { export default {
name: 'MemberSource', name: 'MemberSource',
i18n: {
inherited: __('Inherited'),
directMember: __('Direct member'),
directMemberWithCreatedBy: s__('Members|Direct member by %{createdBy}'),
inheritedMemberWithCreatedBy: s__('Members|%{group} by %{createdBy}'),
},
directives: { directives: {
GlTooltip: GlTooltipDirective, GlTooltip: GlTooltipDirective,
}, },
components: { GlSprintf },
props: { props: {
memberSource: { memberSource: {
type: Object, type: Object,
@ -15,13 +23,40 @@ export default {
type: Boolean, type: Boolean,
required: true, 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> </script>
<template> <template>
<span v-if="isDirectMember">{{ __('Direct member') }}</span> <span v-if="showCreatedBy">
<a v-else v-gl-tooltip.hover :title="__('Inherited')" :href="memberSource.webUrl">{{ <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 memberSource.fullName
}}</a> }}</a>
</template> </template>

View File

@ -4,12 +4,10 @@ import { mapState } from 'vuex';
import MembersTableCell from 'ee_else_ce/members/components/table/members_table_cell.vue'; 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 { canUnban, canOverride, canRemove, canResend, canUpdate } from 'ee_else_ce/members/utils';
import { mergeUrlParams } from '~/lib/utils/url_utility'; import { mergeUrlParams } from '~/lib/utils/url_utility';
import UserDate from '~/vue_shared/components/user_date.vue';
import { import {
FIELD_KEY_ACTIONS, FIELD_KEY_ACTIONS,
FIELDS, FIELDS,
ACTIVE_TAB_QUERY_PARAM_NAME, ACTIVE_TAB_QUERY_PARAM_NAME,
TAB_QUERY_PARAM_VALUES,
MEMBER_STATE_AWAITING, MEMBER_STATE_AWAITING,
MEMBER_STATE_ACTIVE, MEMBER_STATE_ACTIVE,
USER_STATE_BLOCKED, USER_STATE_BLOCKED,
@ -23,6 +21,7 @@ import ExpirationDatepicker from './expiration_datepicker.vue';
import MemberActionButtons from './member_action_buttons.vue'; import MemberActionButtons from './member_action_buttons.vue';
import MemberAvatar from './member_avatar.vue'; import MemberAvatar from './member_avatar.vue';
import MemberSource from './member_source.vue'; import MemberSource from './member_source.vue';
import MemberActivity from './member_activity.vue';
import RoleDropdown from './role_dropdown.vue'; import RoleDropdown from './role_dropdown.vue';
export default { export default {
@ -40,7 +39,7 @@ export default {
RemoveGroupLinkModal, RemoveGroupLinkModal,
RemoveMemberModal, RemoveMemberModal,
ExpirationDatepicker, ExpirationDatepicker,
UserDate, MemberActivity,
LdapOverrideConfirmationModal: () => LdapOverrideConfirmationModal: () =>
import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'), import('ee_component/members/components/ldap/ldap_override_confirmation_modal.vue'),
}, },
@ -80,9 +79,6 @@ export default {
return paramName && currentPage && perPage && totalItems; return paramName && currentPage && perPage && totalItems;
}, },
isInvitedUser() {
return this.tabQueryParamValue === TAB_QUERY_PARAM_VALUES.invite;
},
}, },
methods: { methods: {
hasActionButtons(member) { hasActionButtons(member) {
@ -249,7 +245,11 @@ export default {
<template #cell(source)="{ item: member }"> <template #cell(source)="{ item: member }">
<members-table-cell #default="{ isDirectMember }" :member="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> </members-table-cell>
</template> </template>
@ -281,12 +281,8 @@ export default {
</members-table-cell> </members-table-cell>
</template> </template>
<template #cell(userCreatedAt)="{ item: member }"> <template #cell(activity)="{ item: member }">
<user-date :date="member.user.createdAt" /> <member-activity :member="member" />
</template>
<template #cell(lastActivityOn)="{ item: member }">
<user-date :date="member.user.lastActivityOn" />
</template> </template>
<template #cell(actions)="{ item: member }"> <template #cell(actions)="{ item: member }">
@ -294,7 +290,6 @@ export default {
<member-action-buttons <member-action-buttons
:member-type="memberType" :member-type="memberType"
:is-current-user="isCurrentUser" :is-current-user="isCurrentUser"
:is-invited-user="isInvitedUser"
:permissions="permissions" :permissions="permissions"
:member="member" :member="member"
/> />

View File

@ -20,6 +20,7 @@ export const FIELD_KEY_MAX_ROLE = 'maxRole';
export const FIELD_KEY_USER_CREATED_AT = 'userCreatedAt'; export const FIELD_KEY_USER_CREATED_AT = 'userCreatedAt';
export const FIELD_KEY_LAST_ACTIVITY_ON = 'lastActivityOn'; export const FIELD_KEY_LAST_ACTIVITY_ON = 'lastActivityOn';
export const FIELD_KEY_EXPIRATION = 'expiration'; export const FIELD_KEY_EXPIRATION = 'expiration';
export const FIELD_KEY_ACTIVITY = 'activity';
export const FIELD_KEY_LAST_SIGN_IN = 'lastSignIn'; export const FIELD_KEY_LAST_SIGN_IN = 'lastSignIn';
export const FIELD_KEY_ACTIONS = 'actions'; export const FIELD_KEY_ACTIONS = 'actions';
@ -41,8 +42,6 @@ export const FIELDS = [
{ {
key: FIELD_KEY_GRANTED, key: FIELD_KEY_GRANTED,
label: __('Access granted'), label: __('Access granted'),
thClass: 'col-meta',
tdClass: 'col-meta',
sort: { sort: {
asc: 'last_joined', asc: 'last_joined',
desc: 'oldest_joined', desc: 'oldest_joined',
@ -76,9 +75,15 @@ export const FIELDS = [
thClass: 'col-expiration', thClass: 'col-expiration',
tdClass: '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, key: FIELD_KEY_USER_CREATED_AT,
label: __('Created on'), label: s__('Members|User created'),
sort: { sort: {
asc: 'oldest_created_user', asc: 'oldest_created_user',
desc: 'recent_created_user', desc: 'recent_created_user',

View File

@ -11,7 +11,7 @@ import { groupLinkRequestFormatter } from '~/members/utils';
const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions']; const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
const APP_OPTIONS = { const APP_OPTIONS = {
[MEMBER_TYPES.user]: { [MEMBER_TYPES.user]: {
tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']), tableFields: SHARED_FIELDS.concat(['source', 'activity']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } }, tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: [ tableSortableFields: [
'account', 'account',

View File

@ -20,7 +20,7 @@ initImportProjectMembersTrigger();
const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions']; const SHARED_FIELDS = ['account', 'maxRole', 'expiration', 'actions'];
initMembersApp(document.querySelector('.js-project-members-list-app'), { initMembersApp(document.querySelector('.js-project-members-list-app'), {
[MEMBER_TYPES.user]: { [MEMBER_TYPES.user]: {
tableFields: SHARED_FIELDS.concat(['source', 'granted', 'userCreatedAt', 'lastActivityOn']), tableFields: SHARED_FIELDS.concat(['source', 'activity']),
tableAttrs: { tr: { 'data-qa-selector': 'member_row' } }, tableAttrs: { tr: { 'data-qa-selector': 'member_row' } },
tableSortableFields: [ tableSortableFields: [
'account', 'account',

View File

@ -9,7 +9,7 @@ const INTERVALS = {
export const FILE_SYMLINK_MODE = '120000'; 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'; export const ISO_SHORT_FORMAT = 'yyyy-mm-dd';

View File

@ -76,6 +76,10 @@
width: px-to-rem(200px); width: px-to-rem(200px);
} }
.col-activity {
width: px-to-rem(250px);
}
.col-actions { .col-actions {
width: px-to-rem(65px); width: px-to-rem(65px);
} }

View File

@ -3,7 +3,7 @@
class Import::BulkImportsController < ApplicationController class Import::BulkImportsController < ApplicationController
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
before_action :ensure_group_import_enabled before_action :ensure_bulk_import_enabled
before_action :verify_blocked_uri, only: :status before_action :verify_blocked_uri, only: :status
feature_category :importers feature_category :importers
@ -118,8 +118,8 @@ class Import::BulkImportsController < ApplicationController
] ]
end end
def ensure_group_import_enabled def ensure_bulk_import_enabled
render_404 unless ::BulkImports::Features.enabled? render_404 unless Gitlab::CurrentSettings.bulk_import_enabled?
end end
def access_token_key def access_token_key

View File

@ -16,9 +16,8 @@
#import-group-pane.tab-pane #import-group-pane.tab-pane
- if import_sources_enabled? - if import_sources_enabled?
- if BulkImports::Features.enabled? = render 'import_group_from_another_instance_panel'
= render 'import_group_from_another_instance_panel' .gl-mt-7.gl-border-b-solid.gl-border-gray-100.gl-border-1
.gl-mt-7.gl-border-b-solid.gl-border-gray-100.gl-border-1
= render 'import_group_from_file_panel' = render 'import_group_from_file_panel'
- else - else
.nothing-here-block .nothing-here-block

View File

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

View File

@ -34,7 +34,8 @@ module.exports = {
'pikaday', 'pikaday',
'@gitlab/at.js', '@gitlab/at.js',
'jed', 'jed',
'mermaid', 'mermaid/dist/mermaid.esm.mjs',
'@mermaid-js/mermaid-mindmap/dist/mermaid-mindmap.esm.mjs',
'katex', 'katex',
'three', 'three',
'select2', 'select2',

View File

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

View File

@ -0,0 +1 @@
8e9bb800a2eab9f5d5a3b4f3835b6c4f21ec861a5808a13bef8d496773a7799c

View File

@ -1986,7 +1986,7 @@ On each node perform the following:
{host: '10.6.0.53', port: 26379}, {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_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_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' gitlab_rails['redis_actioncable_instance'] = 'redis://:<REDIS_PRIMARY_PASSWORD_OF_SECOND_CLUSTER>@gitlab-redis-persistent'

View File

@ -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, 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 ## 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. 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 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, 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: WARNING:
The number of tags deleted by this API is limited on GitLab.com The number of tags deleted by this API is limited on GitLab.com

View File

@ -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 Dropping tables can be done safely using a post-deployment migration, but only
if the application no longer uses the table. if the application no longer uses the table.
Add the table to `DELETED_TABLES` in 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).
[gitlab_schema.rb](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/database/gitlab_schema.rb), Even though the table is deleted, it is still referenced in database migrations.
along with its `gitlab_schema`. Even though the table is deleted, it is still
referenced in database migrations.
## Renaming Tables ## Renaming Tables

View File

@ -17,7 +17,7 @@ For the `geo` database, the dictionary files are stored under `ee/db/docs/`.
## Example dictionary file ## Example dictionary file
```yaml ```yaml
--- ----
table_name: terraform_states table_name: terraform_states
classes: classes:
- Terraform::State - Terraform::State
@ -28,45 +28,110 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/26619
milestone: '13.0' 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 ## Adding tables
When adding a new table, create a new file under `db/docs/` for the `main` and `ci` databases. ### Schema
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.
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 ## Dropping tables
When dropping a table, you must remove the metadata file from `db/docs/` for `main` and `ci` databases. ### Schema
For the `geo` database, you must remove the file from `ee/db/docs/`.
Use the same commit with the migration that drops 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. |
| `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 ## 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: When adding a new view, you should:
1. Create a new file for this view in the appropriate directory: 1. Create a new file for this view in the appropriate directory:
- `main` database: `db/docs/views/` - `gitlab_main` view: `db/docs/views/`
- `ci` database: `db/docs/views/` - `gitlab_ci` view: `db/docs/views/`
- `geo` database: `ee/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. 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. 1. Include this file in the commit with the migration that creates the view.
## Dropping views ## Dropping views
When dropping a view, you must remove the metadata file from `db/docs/views/`. ## Schema
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. | 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.

View File

@ -37,6 +37,7 @@ this feature, ask an administrator to [enable the feature flag](../../../adminis
Prerequisites: Prerequisites:
- Network connection between instances or GitLab.com. Must support HTTPS. - 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. - Owner role on the top-level group to migrate.
You can import top-level groups to: You can import top-level groups to:

View File

@ -33,7 +33,7 @@ module API
end end
before do before do
not_found! unless ::BulkImports::Features.enabled? not_found! unless Gitlab::CurrentSettings.bulk_import_enabled?
authenticate! authenticate!
end end

View File

@ -64,67 +64,73 @@ module API
end end
end end
desc 'Start relations export' do resource do
detail 'This feature was introduced in GitLab 13.12' before do
tags %w[group_export] not_found! unless Gitlab::CurrentSettings.bulk_import_enabled?
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.')
end end
end
desc 'Download relations export' do desc 'Start relations export' do
detail 'This feature was introduced in GitLab 13.12' detail 'This feature was introduced in GitLab 13.12'
produces %w[application/octet-stream application/json] tags %w[group_export]
tags %w[group_export] success code: 202
success code: 200 failure [
failure [ { code: 401, message: 'Unauthorized' },
{ code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' },
{ code: 403, message: 'Forbidden' }, { code: 404, message: 'Not found' },
{ code: 404, message: 'Not found' }, { code: 503, message: 'Service unavailable' }
{ 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
end post ':id/export_relations' do
response = ::BulkImports::ExportService.new(portable: user_group, user: current_user).execute
desc 'Relations export status' do if response.success?
detail 'This feature was introduced in GitLab 13.12' accepted!
is_array true else
tags %w[group_export] render_api_error!(message: 'Group relations export could not be started.')
success code: 200, model: Entities::BulkImports::ExportStatus end
failure [ end
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' }, desc 'Download relations export' do
{ code: 404, message: 'Not found' }, detail 'This feature was introduced in GitLab 13.12'
{ code: 503, message: 'Service unavailable' } produces %w[application/octet-stream application/json]
] tags %w[group_export]
end success code: 200
get ':id/export_relations/status' do failure [
present user_group.bulk_import_exports, with: Entities::BulkImports::ExportStatus { 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 end
end end

View File

@ -5,109 +5,114 @@ module API
feature_category :importers feature_category :importers
urgency :low urgency :low
before do
not_found! unless Gitlab::CurrentSettings.project_export_enabled?
authorize_admin_project
end
params do params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
end end
resource :projects, requirements: { id: %r{[^/]+} } do resource :projects, requirements: { id: %r{[^/]+} } do
desc 'Get export status' do resource do
detail 'This feature was introduced in GitLab 10.6.' before do
success code: 200, model: Entities::ProjectExportStatus not_found! unless Gitlab::CurrentSettings.project_export_enabled?
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 authorize_admin_project
detail 'This feature was introduced in GitLab 10.6.' end
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? desc 'Get export status' do
if user_project.export_archive_exists? detail 'This feature was introduced in GitLab 10.6.'
present_carrierwave_file!(user_project.export_file) 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 else
render_api_error!('The project export file is not available yet', 404) render_api_error!('404 Not found or has expired', 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)
end end
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 end
resource do resource do
before do before do
not_found! unless ::Feature.enabled?(:bulk_import) not_found! unless Gitlab::CurrentSettings.bulk_import_enabled?
authorize_admin_project
end end
desc 'Start relations export' do desc 'Start relations export' do

View File

@ -2,10 +2,6 @@
module BulkImports module BulkImports
module Features module Features
def self.enabled?
::Feature.enabled?(:bulk_import)
end
def self.project_migration_enabled?(destination_namespace = nil) def self.project_migration_enabled?(destination_namespace = nil)
if destination_namespace.present? if destination_namespace.present?
root_ancestor = Namespace.find_by_full_path(destination_namespace)&.root_ancestor root_ancestor = Namespace.find_by_full_path(destination_namespace)&.root_ancestor

View File

@ -17,42 +17,6 @@ module Gitlab
module GitlabSchema module GitlabSchema
DICTIONARY_PATH = 'db/docs/' 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) def self.table_schemas(tables)
tables.map { |table| table_schema(table) }.to_set tables.map { |table| table_schema(table) }.to_set
end end
@ -69,13 +33,13 @@ module Gitlab
# strip partition number of a form `loose_foreign_keys_deleted_records_1` # strip partition number of a form `loose_foreign_keys_deleted_records_1`
table_name.gsub!(/_[0-9]+$/, '') 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] if gitlab_schema = views_and_tables_to_schema[table_name]
return gitlab_schema return gitlab_schema
end end
# Tables that are deleted, but we still need to reference them # Tables and views that are deleted, but we still need to reference them
if gitlab_schema = DELETED_TABLES[table_name] if gitlab_schema = deleted_views_and_tables_to_schema[table_name]
return gitlab_schema return gitlab_schema
end end
@ -106,29 +70,51 @@ module Gitlab
[Rails.root.join(DICTIONARY_PATH, 'views', '*.yml')] [Rails.root.join(DICTIONARY_PATH, 'views', '*.yml')]
end 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 def self.views_and_tables_to_schema
@views_and_tables_to_schema ||= self.tables_to_schema.merge(self.views_to_schema) @views_and_tables_to_schema ||= self.tables_to_schema.merge(self.views_to_schema)
end end
def self.tables_to_schema def self.deleted_views_and_tables_to_schema
@tables_to_schema ||= Dir.glob(self.dictionary_path_globs).each_with_object({}) do |file_path, dic| @deleted_views_and_tables_to_schema ||= self.deleted_tables_to_schema.merge(self.deleted_views_to_schema)
data = YAML.load_file(file_path) end
dic[data['table_name']] = data['gitlab_schema'].to_sym def self.deleted_tables_to_schema
end @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 end
def self.views_to_schema def self.views_to_schema
@views_to_schema ||= Dir.glob(self.view_path_globs).each_with_object({}) do |file_path, dic| @views_to_schema ||= self.build_dictionary(self.view_path_globs)
data = YAML.load_file(file_path)
dic[data['view_name']] = data['gitlab_schema'].to_sym
end
end end
def self.schema_names def self.schema_names
@schema_names ||= self.views_and_tables_to_schema.values.to_set @schema_names ||= self.views_and_tables_to_schema.values.to_set
end 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 end
end end

View File

@ -42,7 +42,7 @@ module Gitlab
def should_lock_writes_on_table?(table_name) def should_lock_writes_on_table?(table_name)
# currently gitlab_schema represents only present existing tables, this is workaround for deleted tables # 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. # 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) table_schema = Gitlab::Database::GitlabSchema.table_schema(table_name.to_s, undefined: false)

View File

@ -17,7 +17,8 @@ module Gitlab
HTTP_ERRORS = HTTP_TIMEOUT_ERRORS + [ HTTP_ERRORS = HTTP_TIMEOUT_ERRORS + [
EOFError, SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError, EOFError, SocketError, OpenSSL::SSL::SSLError, OpenSSL::OpenSSLError,
Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ECONNRESET, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH,
Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep Gitlab::HTTP::BlockedUrlError, Gitlab::HTTP::RedirectionTooDeep,
Net::HTTPBadResponse
].freeze ].freeze
DEFAULT_TIMEOUT_OPTIONS = { DEFAULT_TIMEOUT_OPTIONS = {

View File

@ -25797,6 +25797,9 @@ msgstr[1] ""
msgid "Membership" msgid "Membership"
msgstr "" msgstr ""
msgid "Members|%{group} by %{createdBy}"
msgstr ""
msgid "Members|%{time} by %{user}" msgid "Members|%{time} by %{user}"
msgstr "" msgstr ""
@ -25806,6 +25809,12 @@ msgstr ""
msgid "Members|2FA" msgid "Members|2FA"
msgstr "" msgstr ""
msgid "Members|Access granted"
msgstr ""
msgid "Members|Activity"
msgstr ""
msgid "Members|An error occurred while trying to enable LDAP override, please try again." msgid "Members|An error occurred while trying to enable LDAP override, please try again."
msgstr "" msgstr ""
@ -25842,6 +25851,9 @@ msgstr ""
msgid "Members|Direct" msgid "Members|Direct"
msgstr "" msgstr ""
msgid "Members|Direct member by %{createdBy}"
msgstr ""
msgid "Members|Disabled" msgid "Members|Disabled"
msgstr "" msgstr ""
@ -25869,6 +25881,9 @@ msgstr ""
msgid "Members|LDAP override enabled." msgid "Members|LDAP override enabled."
msgstr "" msgstr ""
msgid "Members|Last activity"
msgstr ""
msgid "Members|Leave \"%{source}\"" msgid "Members|Leave \"%{source}\""
msgstr "" msgstr ""
@ -25896,6 +25911,9 @@ msgstr ""
msgid "Members|Search invited" msgid "Members|Search invited"
msgstr "" msgstr ""
msgid "Members|User created"
msgstr ""
msgid "Member|Deny access" msgid "Member|Deny access"
msgstr "" msgstr ""

View File

@ -60,6 +60,7 @@
"@gitlab/ui": "52.6.1", "@gitlab/ui": "52.6.1",
"@gitlab/visual-review-tools": "1.7.3", "@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20221217175648", "@gitlab/web-ide": "0.0.1-dev-20221217175648",
"@mermaid-js/mermaid-mindmap": "^9.3.0",
"@rails/actioncable": "6.1.4-7", "@rails/actioncable": "6.1.4-7",
"@rails/ujs": "6.1.4-7", "@rails/ujs": "6.1.4-7",
"@sourcegraph/code-host-integration": "0.0.84", "@sourcegraph/code-host-integration": "0.0.84",
@ -147,7 +148,7 @@
"marked": "^4.0.18", "marked": "^4.0.18",
"mathjax": "3", "mathjax": "3",
"mdurl": "^1.0.1", "mdurl": "^1.0.1",
"mermaid": "^9.1.3", "mermaid": "^9.3.0",
"micromatch": "^4.0.5", "micromatch": "^4.0.5",
"minimatch": "^3.0.4", "minimatch": "^3.0.4",
"monaco-editor": "^0.30.1", "monaco-editor": "^0.30.1",

View File

@ -2,10 +2,12 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Import::BulkImportsController do RSpec.describe Import::BulkImportsController, feature_category: :importers do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
before do before do
stub_application_setting(bulk_import_enabled: true)
sign_in(user) sign_in(user)
end end
@ -326,9 +328,9 @@ RSpec.describe Import::BulkImportsController do
end end
end end
context 'when bulk_import feature flag is disabled' do context 'when feature is disabled' do
before do before do
stub_feature_flags(bulk_import: false) stub_application_setting(bulk_import_enabled: false)
end end
context 'POST configure' do context 'POST configure' do

View File

@ -8,6 +8,7 @@ RSpec.shared_examples 'validate dictionary' do |objects, directory_path, require
let(:metadata_allowed_fields) do let(:metadata_allowed_fields) do
required_fields + %i[ required_fields + %i[
feature_categories
classes classes
description description
introduced_by_url introduced_by_url
@ -139,3 +140,19 @@ RSpec.describe 'Tables documentation', feature_category: :database do
include_examples 'validate dictionary', tables, directory_path, required_fields include_examples 'validate dictionary', tables, directory_path, required_fields
end 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

View File

@ -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.email)
expect(page).to have_content(current_user.name) 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.email)
expect(page).to have_content(user.name) expect(page).to have_content(user.name)
expect(page).to have_content('Projects') expect(page).to have_content('Projects')

View File

@ -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) } let_it_be(:failed_entity_2) { create(:bulk_import_entity, :failed, bulk_import: user_import_2) }
before do before do
stub_application_setting(bulk_import_enabled: true)
gitlab_sign_in(user) gitlab_sign_in(user)
visit new_group_path visit new_group_path

View File

@ -56,7 +56,7 @@ RSpec.describe 'Groups > Members > Sort members', :js, feature_category: :subgro
expect(first_row.text).to include(owner.name) expect(first_row.text).to include(owner.name)
expect(second_row.text).to include(developer.name) expect(second_row.text).to include(developer.name)
expect_sort_by('Created on', :asc) expect_sort_by('User created', :asc)
end end
it 'sorts by user created on descending' do 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(first_row.text).to include(developer.name)
expect(second_row.text).to include(owner.name) expect(second_row.text).to include(owner.name)
expect_sort_by('Created on', :desc) expect_sort_by('User created', :desc)
end end
it 'sorts by last activity ascending' do it 'sorts by last activity ascending' do

View File

@ -48,7 +48,7 @@ RSpec.describe 'Projects > Members > Sorting', :js, feature_category: :subgroups
expect(first_row.text).to have_content(maintainer.name) expect(first_row.text).to have_content(maintainer.name)
expect(second_row.text).to have_content(developer.name) expect(second_row.text).to have_content(developer.name)
expect_sort_by('Created on', :asc) expect_sort_by('User created', :asc)
end end
it 'sorts by user created on descending' do 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(first_row.text).to have_content(developer.name)
expect(second_row.text).to have_content(maintainer.name) expect(second_row.text).to have_content(maintainer.name)
expect_sort_by('Created on', :desc) expect_sort_by('User created', :desc)
end end
it 'sorts by last activity ascending' do it 'sorts by last activity ascending' do

View File

@ -24,7 +24,7 @@ describe('FormatDate component', () => {
it.each` it.each`
date | dateFormat | output date | dateFormat | output
${mockDate} | ${undefined} | ${'13 Nov, 2020'} ${mockDate} | ${undefined} | ${'Nov 13, 2020'}
${null} | ${undefined} | ${'Never'} ${null} | ${undefined} | ${'Never'}
${undefined} | ${undefined} | ${'Never'} ${undefined} | ${undefined} | ${'Never'}
${mockDate} | ${ISO_SHORT_FORMAT} | ${'2020-11-13'} ${mockDate} | ${ISO_SHORT_FORMAT} | ${'2020-11-13'}

View File

@ -139,6 +139,7 @@ describe('BoardFilteredSearch', () => {
{ type: TOKEN_TYPE_ITERATION, value: { data: 'Any&3', operator: '=' } }, { type: TOKEN_TYPE_ITERATION, value: { data: 'Any&3', operator: '=' } },
{ type: TOKEN_TYPE_RELEASE, value: { data: 'v1.0.0', 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: 'onTrack', operator: '=' } },
{ type: TOKEN_TYPE_HEALTH, value: { data: 'atRisk', operator: '!=' } },
]; ];
jest.spyOn(urlUtility, 'updateHistory'); jest.spyOn(urlUtility, 'updateHistory');
findFilteredSearch().vm.$emit('onFilter', mockFilters); findFilteredSearch().vm.$emit('onFilter', mockFilters);
@ -147,7 +148,7 @@ describe('BoardFilteredSearch', () => {
title: '', title: '',
replace: true, replace: true,
url: 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',
}); });
}); });

View File

@ -16,6 +16,7 @@ import {
TOKEN_TYPE_RELEASE, TOKEN_TYPE_RELEASE,
TOKEN_TYPE_TYPE, TOKEN_TYPE_TYPE,
TOKEN_TYPE_WEIGHT, TOKEN_TYPE_WEIGHT,
TOKEN_TYPE_HEALTH,
} from '~/vue_shared/components/filtered_search_bar/constants'; } from '~/vue_shared/components/filtered_search_bar/constants';
export const getIssuesQueryResponse = { export const getIssuesQueryResponse = {
@ -170,6 +171,8 @@ export const locationSearch = [
'not[weight]=3', 'not[weight]=3',
'crm_contact_id=123', 'crm_contact_id=123',
'crm_organization_id=456', 'crm_organization_id=456',
'health_status=atRisk',
'not[health_status]=onTrack',
].join('&'); ].join('&');
export const locationSearchWithSpecialValues = [ export const locationSearchWithSpecialValues = [
@ -182,6 +185,7 @@ export const locationSearchWithSpecialValues = [
'milestone_title=Upcoming', 'milestone_title=Upcoming',
'epic_id=None', 'epic_id=None',
'weight=None', 'weight=None',
'health_status=None',
].join('&'); ].join('&');
export const filteredTokens = [ export const filteredTokens = [
@ -225,6 +229,8 @@ export const filteredTokens = [
{ type: TOKEN_TYPE_WEIGHT, value: { data: '3', operator: OPERATOR_NOT } }, { type: TOKEN_TYPE_WEIGHT, value: { data: '3', operator: OPERATOR_NOT } },
{ type: TOKEN_TYPE_CONTACT, value: { data: '123', operator: OPERATOR_IS } }, { type: TOKEN_TYPE_CONTACT, value: { data: '123', operator: OPERATOR_IS } },
{ type: TOKEN_TYPE_ORGANIZATION, value: { data: '456', 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: 'find' } },
{ type: FILTERED_SEARCH_TERM, value: { data: 'issues' } }, { 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_MILESTONE, value: { data: 'Upcoming', operator: OPERATOR_IS } },
{ type: TOKEN_TYPE_EPIC, value: { data: 'None', 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_WEIGHT, value: { data: 'None', operator: OPERATOR_IS } },
{ type: TOKEN_TYPE_HEALTH, value: { data: 'None', operator: OPERATOR_IS } },
]; ];
export const apiParams = { export const apiParams = {
@ -255,6 +262,7 @@ export const apiParams = {
weight: '1', weight: '1',
crmContactId: '123', crmContactId: '123',
crmOrganizationId: '456', crmOrganizationId: '456',
healthStatusFilter: 'atRisk',
not: { not: {
authorUsername: 'marge', authorUsername: 'marge',
assigneeUsernames: ['patty', 'selma'], assigneeUsernames: ['patty', 'selma'],
@ -266,6 +274,7 @@ export const apiParams = {
iterationId: ['20', '42'], iterationId: ['20', '42'],
epicId: '34', epicId: '34',
weight: '3', weight: '3',
healthStatusFilter: 'onTrack',
}, },
or: { or: {
authorUsernames: ['burns', 'smithers'], authorUsernames: ['burns', 'smithers'],
@ -283,6 +292,7 @@ export const apiParamsWithSpecialValues = {
milestoneWildcardId: 'UPCOMING', milestoneWildcardId: 'UPCOMING',
epicId: 'None', epicId: 'None',
weight: 'None', weight: 'None',
healthStatusFilter: 'NONE',
}; };
export const urlParams = { export const urlParams = {
@ -311,6 +321,8 @@ export const urlParams = {
'not[weight]': '3', 'not[weight]': '3',
crm_contact_id: '123', crm_contact_id: '123',
crm_organization_id: '456', crm_organization_id: '456',
health_status: 'atRisk',
'not[health_status]': 'onTrack',
}; };
export const urlParamsWithSpecialValues = { export const urlParamsWithSpecialValues = {
@ -323,6 +335,7 @@ export const urlParamsWithSpecialValues = {
milestone_title: 'Upcoming', milestone_title: 'Upcoming',
epic_id: 'None', epic_id: 'None',
weight: 'None', weight: 'None',
health_status: 'None',
}; };
export const project1 = { export const project1 = {

View File

@ -38,7 +38,6 @@ describe('AccessRequestActionButtons', () => {
title: 'Deny access', title: 'Deny access',
isAccessRequest: true, isAccessRequest: true,
isInvite: false, isInvite: false,
icon: 'close',
}); });
}); });

View File

@ -44,7 +44,6 @@ describe('InviteActionButtons', () => {
title: 'Revoke invite', title: 'Revoke invite',
isAccessRequest: false, isAccessRequest: false,
isInvite: true, isInvite: true,
icon: 'remove',
}); });
}); });
}); });

View File

@ -79,18 +79,4 @@ describe('RemoveMemberButton', () => {
expect(actions.showRemoveMemberModal).toHaveBeenCalledWith(expect.any(Object), modalData); 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');
});
});
}); });

View File

@ -43,12 +43,9 @@ describe('UserActionButtons', () => {
memberId: member.id, memberId: member.id,
memberType: 'GroupMember', memberType: 'GroupMember',
message: `Are you sure you want to remove ${member.user.name} from "${member.source.fullName}"?`, message: `Are you sure you want to remove ${member.user.name} from "${member.source.fullName}"?`,
title: null, title: UserActionButtons.i18n.title,
isAccessRequest: false, isAccessRequest: false,
isInvite: false, isInvite: false,
icon: '',
buttonCategory: 'secondary',
buttonText: 'Remove member',
userDeletionObstacles: { userDeletionObstacles: {
name: member.user.name, name: member.user.name,
obstacles: parseUserDeletionObstacles(member.user), obstacles: parseUserDeletionObstacles(member.user),
@ -132,30 +129,4 @@ describe('UserActionButtons', () => {
expect(findRemoveMemberButton().props().memberType).toBe('ProjectMember'); 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,
}),
);
},
);
});
}); });

View File

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

View File

@ -1,20 +1,18 @@
import { within } from '@testing-library/dom'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { mount, createWrapper } from '@vue/test-utils';
import { useFakeDate } from 'helpers/fake_date'; import { useFakeDate } from 'helpers/fake_date';
import CreatedAt from '~/members/components/table/created_at.vue'; import CreatedAt from '~/members/components/table/created_at.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
describe('CreatedAt', () => { describe('CreatedAt', () => {
// March 15th, 2020 // March 15th, 2020
useFakeDate(2020, 2, 15); useFakeDate(2020, 2, 15);
const date = '2020-03-01T00:00:00.000'; const date = '2020-03-01T00:00:00.000';
const dateTimeAgo = '2 weeks ago'; const formattedDate = 'Mar 01, 2020';
let wrapper; let wrapper;
const createComponent = (propsData) => { const createComponent = (propsData) => {
wrapper = mount(CreatedAt, { wrapper = mountExtended(CreatedAt, {
propsData: { propsData: {
date, date,
...propsData, ...propsData,
@ -22,9 +20,6 @@ describe('CreatedAt', () => {
}); });
}; };
const getByText = (text, options) =>
createWrapper(within(wrapper.element).getByText(text, options));
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
}); });
@ -35,11 +30,7 @@ describe('CreatedAt', () => {
}); });
it('displays created at text', () => { it('displays created at text', () => {
expect(getByText(dateTimeAgo).exists()).toBe(true); expect(wrapper.findByText(formattedDate).exists()).toBe(true);
});
it('uses `TimeAgoTooltip` component to display tooltip', () => {
expect(wrapper.findComponent(TimeAgoTooltip).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.exists()).toBe(true);
expect(link.attributes('href')).toBe('https://gitlab.com/root'); expect(link.attributes('href')).toBe('https://gitlab.com/root');

View File

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

View File

@ -1,19 +1,25 @@
import { getByText as getByTextHelper } from '@testing-library/dom'; import { mountExtended } from 'helpers/vue_test_utils_helper';
import { mount, createWrapper } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import MemberSource from '~/members/components/table/member_source.vue'; import MemberSource from '~/members/components/table/member_source.vue';
describe('MemberSource', () => { describe('MemberSource', () => {
let wrapper; 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) => { const createComponent = (propsData) => {
wrapper = mount(MemberSource, { wrapper = mountExtended(MemberSource, {
propsData: { propsData: {
memberSource: { memberSource,
id: 102,
fullName: 'Foo bar',
webUrl: 'https://gitlab.com/groups/foo-bar',
},
...propsData, ...propsData,
}, },
directives: { 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'); const getTooltipDirective = (elementWrapper) => getBinding(elementWrapper.element, 'gl-tooltip');
afterEach(() => { afterEach(() => {
@ -32,40 +35,69 @@ describe('MemberSource', () => {
}); });
describe('direct member', () => { describe('direct member', () => {
it('displays "Direct member"', () => { describe('when created by is available', () => {
createComponent({ it('displays "Direct member by <user name>"', () => {
isDirectMember: true, 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', () => { describe('inherited member', () => {
let sourceGroupLink; describe('when created by is available', () => {
beforeEach(() => {
beforeEach(() => { createComponent({
createComponent({ isDirectMember: false,
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', () => { describe('when created by is not available', () => {
createComponent({ beforeEach(() => {
isDirectMember: false, createComponent({
isDirectMember: false,
});
}); });
expect(sourceGroupLink.exists()).toBe(true); it('displays a link to source group', () => {
expect(sourceGroupLink.attributes('href')).toBe('https://gitlab.com/groups/foo-bar'); expect(wrapper.text()).toBe(memberSource.fullName);
}); expect(wrapper.attributes('href')).toBe(memberSource.webUrl);
});
it('displays tooltip with "Inherited"', () => { it('displays tooltip with "Inherited"', () => {
const tooltipDirective = getTooltipDirective(sourceGroupLink); const tooltipDirective = getTooltipDirective(wrapper);
expect(tooltipDirective).not.toBeUndefined(); expect(tooltipDirective).not.toBeUndefined();
expect(sourceGroupLink.attributes('title')).toBe('Inherited'); expect(tooltipDirective.value).toBe('Inherited');
});
}); });
}); });
}); });

View File

@ -8,9 +8,9 @@ import ExpirationDatepicker from '~/members/components/table/expiration_datepick
import MemberActionButtons from '~/members/components/table/member_action_buttons.vue'; import MemberActionButtons from '~/members/components/table/member_action_buttons.vue';
import MemberAvatar from '~/members/components/table/member_avatar.vue'; import MemberAvatar from '~/members/components/table/member_avatar.vue';
import MemberSource from '~/members/components/table/member_source.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 MembersTable from '~/members/components/table/members_table.vue';
import RoleDropdown from '~/members/components/table/role_dropdown.vue'; import RoleDropdown from '~/members/components/table/role_dropdown.vue';
import UserDate from '~/vue_shared/components/user_date.vue';
import { import {
MEMBER_TYPES, MEMBER_TYPES,
MEMBER_STATE_CREATED, MEMBER_STATE_CREATED,
@ -106,16 +106,14 @@ describe('MembersTable', () => {
}; };
it.each` it.each`
field | label | member | expectedComponent field | label | member | expectedComponent
${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar} ${'account'} | ${'Account'} | ${memberMock} | ${MemberAvatar}
${'source'} | ${'Source'} | ${memberMock} | ${MemberSource} ${'source'} | ${'Source'} | ${memberMock} | ${MemberSource}
${'granted'} | ${'Access granted'} | ${memberMock} | ${CreatedAt} ${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt}
${'invited'} | ${'Invited'} | ${invite} | ${CreatedAt} ${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt}
${'requested'} | ${'Requested'} | ${accessRequest} | ${CreatedAt} ${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown}
${'maxRole'} | ${'Max role'} | ${memberCanUpdate} | ${RoleDropdown} ${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker}
${'expiration'} | ${'Expiration'} | ${memberMock} | ${ExpirationDatepicker} ${'activity'} | ${'Activity'} | ${memberMock} | ${MemberActivity}
${'userCreatedAt'} | ${'Created on'} | ${memberMock} | ${UserDate}
${'lastActivityOn'} | ${'Last activity'} | ${memberMock} | ${UserDate}
`('renders the $label field', ({ field, label, member, expectedComponent }) => { `('renders the $label field', ({ field, label, member, expectedComponent }) => {
createComponent({ createComponent({
members: [member], members: [member],

View File

@ -8,13 +8,21 @@ RSpec.shared_examples 'validate path globs' do |path_globs|
end end
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 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 describe '.views_and_tables_to_schema' do
it 'all tables and views have assigned a known gitlab_schema' do include_examples 'validate schema data', described_class.views_and_tables_to_schema
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
# This being run across different databases indirectly also tests # This being run across different databases indirectly also tests
# a general consistency of structure across databases # 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 include_examples 'validate path globs', described_class.view_path_globs
end 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 describe '.tables_to_schema' do
let(:database_models) { Gitlab::Database.database_base_models.except(:geo) } let(:database_models) { Gitlab::Database.database_base_models.except(:geo) }
let(:views) { database_models.flat_map { |_, m| m.connection.views }.sort.uniq } let(:views) { database_models.flat_map { |_, m| m.connection.views }.sort.uniq }

View File

@ -95,7 +95,9 @@ RSpec.describe Gitlab::Database::MigrationHelpers::AutomaticLockWritesOnTables,
context 'when table listed as a deleted table' do context 'when table listed as a deleted table' do
before 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 end
it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:ci] 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 context 'when table listed as a deleted table' do
before 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 end
it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main] it_behaves_like 'does not lock writes on table', Gitlab::Database.database_base_models[:main]

View File

@ -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(:entity_3) { create(:bulk_import_entity, bulk_import: import_2) }
let_it_be(:failure_3) { create(:bulk_import_failure, entity: entity_3) } 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 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 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(response).to have_gitlab_http_status(:ok)
expect(json_response.pluck('id')).to contain_exactly(import_1.id, import_2.id) 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 context 'sort parameter' do
it 'sorts by created_at descending by default' 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(response).to have_gitlab_http_status(:ok)
expect(json_response.pluck('id')).to eq([import_2.id, import_1.id]) expect(json_response.pluck('id')).to eq([import_2.id, import_1.id])
end end
it 'sorts by created_at descending when explicitly specified' do context 'when explicitly specified' do
get api('/bulk_imports', user), params: { sort: 'desc' } context 'when descending' do
let(:params) { { sort: 'desc' } }
expect(response).to have_gitlab_http_status(:ok) it 'sorts by created_at descending' do
expect(json_response.pluck('id')).to eq([import_2.id, import_1.id]) request
end
it 'sorts by created_at ascending when explicitly specified' do expect(response).to have_gitlab_http_status(:ok)
get api('/bulk_imports', user), params: { sort: 'asc' } expect(json_response.pluck('id')).to match_array([import_2.id, import_1.id])
end
end
expect(response).to have_gitlab_http_status(:ok) context 'when ascending' do
expect(json_response.pluck('id')).to eq([import_1.id, import_2.id]) 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
end end
include_examples 'disabled feature'
end end
describe 'POST /bulk_imports' do describe 'POST /bulk_imports' do
@ -56,21 +85,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do
end end
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 shared_examples 'starting a new migration' do
it 'starts a new migration' do let(:request) { post api('/bulk_imports', user), params: params }
post api('/bulk_imports', user), params: { let(:params) do
{
configuration: { configuration: {
url: 'http://gitlab.example', url: 'http://gitlab.example',
access_token: 'access_token' access_token: 'access_token'
@ -83,6 +101,10 @@ RSpec.describe API::BulkImports, feature_category: :importers do
}.merge(destination_param) }.merge(destination_param)
] ]
} }
end
it 'starts a new migration' do
request
expect(response).to have_gitlab_http_status(:created) expect(response).to have_gitlab_http_status(:created)
@ -99,8 +121,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do
end end
context 'when both destination_name & destination_slug are provided' do context 'when both destination_name & destination_slug are provided' do
it 'returns a mutually exclusive error' do let(:params) do
post api('/bulk_imports', user), params: { {
configuration: { configuration: {
url: 'http://gitlab.example', url: 'http://gitlab.example',
access_token: 'access_token' 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) expect(response).to have_gitlab_http_status(:bad_request)
@ -123,8 +149,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do
end end
context 'when neither destination_name nor destination_slug is provided' do context 'when neither destination_name nor destination_slug is provided' do
it 'returns at_least_one_of error' do let(:params) do
post api('/bulk_imports', user), params: { {
configuration: { configuration: {
url: 'http://gitlab.example', url: 'http://gitlab.example',
access_token: 'access_token' 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) expect(response).to have_gitlab_http_status(:bad_request)
@ -145,8 +175,8 @@ RSpec.describe API::BulkImports, feature_category: :importers do
end end
context 'when provided url is blocked' do context 'when provided url is blocked' do
it 'returns blocked url error' do let(:params) do
post api('/bulk_imports', user), params: { {
configuration: { configuration: {
url: 'url', url: 'url',
access_token: 'access_token' access_token: 'access_token'
@ -158,49 +188,71 @@ RSpec.describe API::BulkImports, feature_category: :importers do
destination_namespace: 'destination_namespace' destination_namespace: 'destination_namespace'
] ]
} }
end
it 'returns blocked url error' do
request
expect(response).to have_gitlab_http_status(:unprocessable_entity) 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') expect(json_response['message']).to eq('Validation failed: Url is blocked: Only allowed schemes are http, https')
end end
end end
include_examples 'disabled feature'
end end
describe 'GET /bulk_imports/entities' do 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 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(response).to have_gitlab_http_status(:ok)
expect(json_response.pluck('id')).to contain_exactly(entity_1.id, entity_2.id, entity_3.id) expect(json_response.pluck('id')).to contain_exactly(entity_1.id, entity_2.id, entity_3.id)
end end
include_examples 'disabled feature'
end end
describe 'GET /bulk_imports/:id' do describe 'GET /bulk_imports/:id' do
let(:request) { get api("/bulk_imports/#{import_1.id}", user) }
it 'returns specified bulk import' do it 'returns specified bulk import' do
get api("/bulk_imports/#{import_1.id}", user) request
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(import_1.id) expect(json_response['id']).to eq(import_1.id)
end end
include_examples 'disabled feature'
end end
describe 'GET /bulk_imports/:id/entities' do 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 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(response).to have_gitlab_http_status(:ok)
expect(json_response.pluck('id')).to contain_exactly(entity_3.id) 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) expect(json_response.first['failures'].first['exception_class']).to eq(failure_3.exception_class)
end end
include_examples 'disabled feature'
end end
describe 'GET /bulk_imports/:id/entities/:entity_id' do 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 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(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(entity_2.id) expect(json_response['id']).to eq(entity_2.id)
end end
include_examples 'disabled feature'
end end
context 'when user is unauthenticated' do context 'when user is unauthenticated' do

View File

@ -173,6 +173,8 @@ RSpec.describe API::GroupExport, feature_category: :importers do
let(:status_path) { "/groups/#{group.id}/export_relations/status" } let(:status_path) { "/groups/#{group.id}/export_relations/status" }
before do before do
stub_application_setting(bulk_import_enabled: true)
group.add_owner(user) group.add_owner(user)
end end
@ -212,11 +214,12 @@ RSpec.describe API::GroupExport, feature_category: :importers do
context 'when export_file.file does not exist' do context 'when export_file.file does not exist' do
it 'returns 404' 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) get api(download_path, user)
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['message']).to eq('404 Not found')
end end
end 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) expect(json_response.pluck('status')).to contain_exactly(-1, 0, 1)
end 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
end end

View File

@ -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" } 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 context 'when user is a maintainer' do
before do before do
project.add_maintainer(user) project.add_maintainer(user)
@ -584,9 +588,9 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category:
end end
end end
context 'with bulk_import FF disabled' do context 'with bulk_import is disabled' do
before do before do
stub_feature_flags(bulk_import: false) stub_application_setting(bulk_import_enabled: false)
end end
describe 'POST /projects/:id/export_relations' do describe 'POST /projects/:id/export_relations' do
@ -641,5 +645,11 @@ RSpec.describe API::ProjectExport, :clean_gitlab_redis_cache, feature_category:
end end
end 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
end end

172
yarn.lock
View File

@ -1627,6 +1627,19 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg== 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": "@miragejs/pretender-node-polyfill@^0.1.0":
version "0.1.2" version "0.1.2"
resolved "https://registry.yarnpkg.com/@miragejs/pretender-node-polyfill/-/pretender-node-polyfill-0.1.2.tgz#d26b6b7483fb70cd62189d05c95d2f67153e43f2" 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" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== 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: cosmiconfig-toml-loader@1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/cosmiconfig-toml-loader/-/cosmiconfig-toml-loader-1.0.0.tgz#0681383651cceff918177debe9084c0d3769509b" 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" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= 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: d3-array@1, "d3-array@1 - 2", d3-array@^1.1.1, d3-array@^1.2.0:
version "1.2.4" version "1.2.4"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw== integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
"d3-array@2 - 3", "d3-array@2.10.0 - 3", "d3-array@2.5.0 - 3", d3-array@3: "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.0.4" version "3.2.1"
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.0.4.tgz#60550bcc9818be9ace88d269ccd97038fc399b55" resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.1.tgz#39331ea706f5709417d31bbb6ec152e0328b39b3"
integrity sha512-ShFl90cxNqDaSynDF/Bik/kTzISqePqU3qo2fv6kSJEvF7y7tDCDpcU6WiT01rPO6zngZnrvJ/0j4q6Qg+5EQg== integrity sha512-gUY/qeHq/yNqqoCKNq4vtpFLdoCdvyNpWoC/KNjhGbhDuQpAM9sIQQKkXSNpXa9h5KySs/gzm7R88WkUutgwWQ==
dependencies: dependencies:
internmap "1 - 2" internmap "1 - 2"
@ -4326,12 +4375,12 @@ d3-contour@1:
dependencies: dependencies:
d3-array "^1.1.1" d3-array "^1.1.1"
d3-contour@3: d3-contour@4:
version "3.0.1" version "4.0.0"
resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-3.0.1.tgz#2c64255d43059599cd0dba8fe4cc3d51ccdd9bbd" resolved "https://registry.yarnpkg.com/d3-contour/-/d3-contour-4.0.0.tgz#5a1337c6da0d528479acdb5db54bc81a0ff2ec6b"
integrity sha512-0Oc4D0KyhwhM7ZL0RMnfGycLN7hxHB8CMmwZ3+H26PWAG0ozNuYG5hXSDNgmP1SgJkQMrlG6cP20HoaSbvcJTQ== integrity sha512-7aQo0QHUTu/Ko3cP9YK9yUTxtoDEiDGwnBHyLxG5M4vqlBkO/uixMRele3nfsfj6UXOcuReVpVXzAboGraYIJw==
dependencies: dependencies:
d3-array "2 - 3" d3-array "^3.2.0"
d3-delaunay@6: d3-delaunay@6:
version "6.0.2" version "6.0.2"
@ -4672,7 +4721,7 @@ d3-zoom@3:
d3-selection "2 - 3" d3-selection "2 - 3"
d3-transition "2 - 3" d3-transition "2 - 3"
d3@^5.14, d3@^5.16.0: d3@^5.16.0:
version "5.16.0" version "5.16.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877" resolved "https://registry.yarnpkg.com/d3/-/d3-5.16.0.tgz#9c5e8d3b56403c79d4ed42fbd62f6113f199c877"
integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw== integrity sha512-4PL5hHaHwX4m7Zr1UapXW23apo6pexCgdetdJ5kTmADpG/7T9Gkxw0M0tf/pjoB63ezCCm0u5UaFYy2aMt0Mcw==
@ -4709,17 +4758,17 @@ d3@^5.14, d3@^5.16.0:
d3-voronoi "1" d3-voronoi "1"
d3-zoom "1" d3-zoom "1"
d3@^7.0.0: d3@^7.0.0, d3@^7.7.0:
version "7.0.4" version "7.7.0"
resolved "https://registry.yarnpkg.com/d3/-/d3-7.0.4.tgz#37dfeb3b526f64a0de2ddb705ea61649325207bd" resolved "https://registry.yarnpkg.com/d3/-/d3-7.7.0.tgz#e7779a74ea7c807b432fdfd8128de062b19c62eb"
integrity sha512-ruRiyPYZEGeJBOOjVS5pHliNUZM2HAllEY7HKB2ff+9ENxOti4N+S+WZqo9ggUMr8tSPMm+riqKpJd1oYEDN5Q== integrity sha512-VEwHCMgMjD2WBsxeRGUE18RmzxT9Bn7ghDpzvTEvkLSBAKgTMydJjouZTjspgQfRHpPt/PB3EHWBa6SSyFQq4g==
dependencies: dependencies:
d3-array "3" d3-array "3"
d3-axis "3" d3-axis "3"
d3-brush "3" d3-brush "3"
d3-chord "3" d3-chord "3"
d3-color "3" d3-color "3"
d3-contour "3" d3-contour "4"
d3-delaunay "6" d3-delaunay "6"
d3-dispatch "3" d3-dispatch "3"
d3-drag "3" d3-drag "3"
@ -4745,23 +4794,13 @@ d3@^7.0.0:
d3-transition "3" d3-transition "3"
d3-zoom "3" d3-zoom "3"
dagre-d3@^0.6.4: dagre-d3-es@7.0.6:
version "0.6.4" version "7.0.6"
resolved "https://registry.yarnpkg.com/dagre-d3/-/dagre-d3-0.6.4.tgz#0728d5ce7f177ca2337df141ceb60fbe6eeb7b29" resolved "https://registry.yarnpkg.com/dagre-d3-es/-/dagre-d3-es-7.0.6.tgz#8cab465ff95aca8a1ca2292d07e1fb31b5db83f2"
integrity sha512-e/6jXeCP7/ptlAM48clmX4xTZc5Ek6T6kagS7Oz2HrYSdqcLZFLqpAfh7ldbZRFfxCZVyh61NEPR08UQRVxJzQ== integrity sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==
dependencies: dependencies:
d3 "^5.14" d3 "^7.7.0"
dagre "^0.8.5" lodash-es "^4.17.21"
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"
data-urls@^3.0.1: data-urls@^3.0.1:
version "3.0.2" version "3.0.2"
@ -5090,12 +5129,7 @@ dommatrix@^1.0.3:
resolved "https://registry.yarnpkg.com/dommatrix/-/dommatrix-1.0.3.tgz#e7c18e8d6f3abdd1fef3dd4aa74c4d2e620a0525" resolved "https://registry.yarnpkg.com/dommatrix/-/dommatrix-1.0.3.tgz#e7c18e8d6f3abdd1fef3dd4aa74c4d2e620a0525"
integrity sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww== integrity sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==
dompurify@2.3.8: dompurify@2.4.1, dompurify@^2.4.1:
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:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631"
integrity sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA== 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" resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== 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: graphql-config@^4.3.6:
version "4.3.6" version "4.3.6"
resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-4.3.6.tgz#908ef03d6670c3068e51fe2e84e10e3e0af220b6" 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" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 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: highlight.js@^11.5.1, highlight.js@~11.5.0:
version "11.5.1" version "11.5.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.5.1.tgz#027c24e4509e2f4dcd00b4a6dda542ce0a1f7aea" 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" resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776"
integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA== 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: leven@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
@ -8153,6 +8195,11 @@ locate-path@^6.0.0:
dependencies: dependencies:
p-locate "^5.0.0" 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: lodash.assign@^4.2.0:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" 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" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
mermaid@^9.1.3: mermaid@^9.3.0:
version "9.1.3" version "9.3.0"
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.1.3.tgz#15d08662c66250124ce31106a4620285061ac59c" resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.3.0.tgz#8bd7c4a44b53e4e85c53a0a474442e9c273494ae"
integrity sha512-jTIYiqKwsUXVCoxHUVkK8t0QN3zSKIdJlb9thT0J5jCnzXyc+gqTbZE2QmjRfavFTPPn5eRy5zaFp7V+6RhxYg== integrity sha512-mGl0BM19TD/HbU/LmlaZbjBi//tojelg8P/mxD6pPZTAYaI+VawcyBdqRsoUHSc7j71PrMdJ3HBadoQNdvP5cg==
dependencies: dependencies:
"@braintree/sanitize-url" "^6.0.0" "@braintree/sanitize-url" "^6.0.0"
d3 "^7.0.0" d3 "^7.0.0"
dagre "^0.8.5" dagre-d3-es "7.0.6"
dagre-d3 "^0.6.4" dompurify "2.4.1"
dompurify "2.3.8"
graphlib "^2.1.8"
khroma "^2.0.0" khroma "^2.0.0"
lodash-es "^4.17.21"
moment-mini "^2.24.0" 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: meros@^1.1.4:
version "1.2.0" version "1.2.0"
@ -9428,6 +9476,11 @@ nomnom@^1.5.x:
chalk "~0.4.0" chalk "~0.4.0"
underscore "~1.6.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: nopt@^4.0.3:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" 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" v8-compile-cache "^2.3.0"
write-file-atomic "^4.0.1" write-file-atomic "^4.0.1"
stylis@^4.0.10: stylis@^4.1.2:
version "4.0.10" version "4.1.3"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7"
integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg== integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==
subscriptions-transport-ws@^0.11.0: subscriptions-transport-ws@^0.11.0:
version "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" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== 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: uvu@^0.5.0:
version "0.5.3" version "0.5.3"
resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.3.tgz#3d83c5bc1230f153451877bfc7f4aea2392219ae" resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.3.tgz#3d83c5bc1230f153451877bfc7f4aea2392219ae"