Welcome to mirror list, hosted at ThFree Co, Russian Federation.

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