summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-05-05 15:09:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-05-05 15:09:42 +0000
commit0d09054d162458364ce3fc431506c182e2a5fa4f (patch)
tree876e3bd5b2aae32189c09e113454a58c91cbd3f6
parentf44809bf96e636a28394c9849dbea5f68e8c910c (diff)
downloadgitlab-ce-0d09054d162458364ce3fc431506c182e2a5fa4f.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml12
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml1
-rw-r--r--app/assets/javascripts/registry/explorer/constants.js35
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue126
-rw-r--r--app/assets/javascripts/registry/explorer/pages/index.vue39
-rw-r--r--app/assets/javascripts/registry/explorer/stores/actions.js4
-rw-r--r--app/models/group.rb1
-rw-r--r--app/models/group_wiki.rb27
-rw-r--r--app/views/projects/issues/show.html.haml4
-rw-r--r--app/views/projects/static_site_editor/show.html.haml5
-rw-r--r--changelogs/unreleased/214477-reactor-toast-to-alert-in-image-repository-details-page.yml5
-rw-r--r--changelogs/unreleased/fix-issue-details-row-emoji-block.yml5
-rw-r--r--changelogs/unreleased/sh-revert-codeowners-check.yml5
-rw-r--r--doc/development/testing_guide/review_apps.md12
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb31
-rw-r--r--lib/gitlab/metrics/system.rb81
-rw-r--r--lib/gitlab/static_site_editor/config.rb7
-rw-r--r--lib/gitlab/url_builder.rb7
-rw-r--r--locale/gitlab.pot24
-rw-r--r--package.json2
-rw-r--r--spec/factories/groups.rb6
-rw-r--r--spec/factories/wikis.rb4
-rw-r--r--spec/features/groups/members/leave_group_spec.rb2
-rw-r--r--spec/frontend/registry/explorer/pages/details_spec.js150
-rw-r--r--spec/frontend/registry/explorer/pages/index_spec.js36
-rw-r--r--spec/frontend/registry/explorer/stores/actions_spec.js20
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb17
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb113
-rw-r--r--spec/lib/gitlab/repository_url_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/static_site_editor/config_spec.rb18
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb1
-rw-r--r--spec/models/group_spec.rb5
-rw-r--r--spec/models/group_wiki_spec.rb38
-rw-r--r--spec/models/wiki_page_spec.rb31
-rw-r--r--yarn.lock8
37 files changed, 498 insertions, 389 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index d9b0185a8d8..21dfd6563e2 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -15,7 +15,7 @@ stages:
# in cases where jobs require Docker-in-Docker, the job
# definition must be extended with `.use-docker-in-docker`
default:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
tags:
- gitlab-org
# All jobs are interruptible by default
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 2df24eedc1f..5be4d4eaf1f 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -15,7 +15,7 @@
- .default-retry
- .default-before_script
- .assets-compile-cache
- image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-graphicsmagick-1.3.34-docker-19.03.1
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-graphicsmagick-1.3.34-docker-19.03.1
stage: prepare
variables:
NODE_ENV: "production"
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 21d085c5ca5..0c26273ef04 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -30,7 +30,7 @@
policy: pull
.use-pg9:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
services:
- name: postgres:9.6.17
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -41,7 +41,7 @@
key: "debian-stretch-ruby-2.6.6-pg9-node-12.x"
.use-pg10:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
services:
- name: postgres:10.12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -52,7 +52,7 @@
key: "debian-stretch-ruby-2.6.6-pg10-node-12.x"
.use-pg11:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -63,7 +63,7 @@
key: "debian-stretch-ruby-2.6.6-pg11-node-12.x"
.use-pg9-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-9.6-graphicsmagick-1.3.34"
services:
- name: postgres:9.6.17
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -75,7 +75,7 @@
key: "debian-stretch-ruby-2.6.6-pg9-node-12.x"
.use-pg10-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-10-graphicsmagick-1.3.34"
services:
- name: postgres:10.12
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
@@ -87,7 +87,7 @@
key: "debian-stretch-ruby-2.6.6-pg10-node-12.x"
.use-pg11-ee:
- image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-81.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
+ image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.6-golang-1.14-git-2.26-lfs-2.9-chrome-73.0-node-12.x-yarn-1.21-postgresql-11-graphicsmagick-1.3.34"
services:
- name: postgres:11.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index f2de98cbfab..c63397dceea 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -145,7 +145,6 @@ db:check-schema:
- .db-job-base
- .rails:rules:ee-mr-and-master-only
script:
- - scripts/regenerate-schema
- source scripts/schema_changed.sh
db:migrate-from-v11.11.0:
diff --git a/app/assets/javascripts/registry/explorer/constants.js b/app/assets/javascripts/registry/explorer/constants.js
index d4b9d25b212..4ca4c7088a6 100644
--- a/app/assets/javascripts/registry/explorer/constants.js
+++ b/app/assets/javascripts/registry/explorer/constants.js
@@ -39,14 +39,20 @@ export const DELETE_IMAGE_SUCCESS_MESSAGE = s__(
// Image details page
+export const DETAILS_PAGE_TITLE = s__('ContainerRegistry|%{imageName} tags');
+
export const DELETE_TAG_ERROR_MESSAGE = s__(
- 'ContainerRegistry|Something went wrong while deleting the tag.',
+ 'ContainerRegistry|Something went wrong while marking the tag for deletion.',
+);
+export const DELETE_TAG_SUCCESS_MESSAGE = s__(
+ 'ContainerRegistry|Tag successfully marked for deletion.',
);
-export const DELETE_TAG_SUCCESS_MESSAGE = s__('ContainerRegistry|Tag deleted successfully');
export const DELETE_TAGS_ERROR_MESSAGE = s__(
- 'ContainerRegistry|Something went wrong while deleting the tags.',
+ 'ContainerRegistry|Something went wrong while marking the tags for deletion.',
+);
+export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
+ 'ContainerRegistry|Tags successfully marked for deletion.',
);
-export const DELETE_TAGS_SUCCESS_MESSAGE = s__('ContainerRegistry|Tags deleted successfully');
export const DEFAULT_PAGE = 1;
export const DEFAULT_PAGE_SIZE = 10;
@@ -65,6 +71,27 @@ export const LIST_LABEL_IMAGE_ID = s__('ContainerRegistry|Image ID');
export const LIST_LABEL_SIZE = s__('ContainerRegistry|Compressed Size');
export const LIST_LABEL_LAST_UPDATED = s__('ContainerRegistry|Last Updated');
+export const REMOVE_TAG_BUTTON_TITLE = s__('ContainerRegistry|Remove tag');
+export const REMOVE_TAGS_BUTTON_TITLE = s__('ContainerRegistry|Remove selected tags');
+
+export const REMOVE_TAG_CONFIRMATION_TEXT = s__(
+ `ContainerRegistry|You are about to remove %{item}. Are you sure?`,
+);
+export const REMOVE_TAGS_CONFIRMATION_TEXT = s__(
+ `ContainerRegistry|You are about to remove %{item} tags. Are you sure?`,
+);
+
+export const EMPTY_IMAGE_REPOSITORY_TITLE = s__('ContainerRegistry|This image has no active tags');
+export const EMPTY_IMAGE_REPOSITORY_MESSAGE = s__(
+ `ContainerRegistry|The last tag related to this image was recently removed.
+This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
+If you have any questions, contact your administrator.`,
+);
+
+export const ADMIN_GARBAGE_COLLECTION_TIP = s__(
+ 'ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
+);
+
// Expiration policies
export const EXPIRATION_POLICY_ALERT_TITLE = s__(
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index 6afd4d1107a..1d4dcd9a7a2 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -9,12 +9,14 @@ import {
GlPagination,
GlModal,
GlSprintf,
+ GlAlert,
+ GlLink,
GlEmptyState,
GlResizeObserverDirective,
GlSkeletonLoader,
} from '@gitlab/ui';
import { GlBreakpointInstance } from '@gitlab/ui/dist/utils';
-import { n__, s__ } from '~/locale';
+import { n__ } from '~/locale';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
@@ -35,6 +37,14 @@ import {
DELETE_TAG_ERROR_MESSAGE,
DELETE_TAGS_SUCCESS_MESSAGE,
DELETE_TAGS_ERROR_MESSAGE,
+ REMOVE_TAG_CONFIRMATION_TEXT,
+ REMOVE_TAGS_CONFIRMATION_TEXT,
+ DETAILS_PAGE_TITLE,
+ REMOVE_TAGS_BUTTON_TITLE,
+ REMOVE_TAG_BUTTON_TITLE,
+ EMPTY_IMAGE_REPOSITORY_TITLE,
+ EMPTY_IMAGE_REPOSITORY_MESSAGE,
+ ADMIN_GARBAGE_COLLECTION_TIP,
} from '../constants';
export default {
@@ -49,6 +59,8 @@ export default {
GlSkeletonLoader,
GlSprintf,
GlEmptyState,
+ GlAlert,
+ GlLink,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -60,6 +72,19 @@ export default {
width: 1000,
height: 40,
},
+ i18n: {
+ DETAILS_PAGE_TITLE,
+ REMOVE_TAGS_BUTTON_TITLE,
+ REMOVE_TAG_BUTTON_TITLE,
+ EMPTY_IMAGE_REPOSITORY_TITLE,
+ EMPTY_IMAGE_REPOSITORY_MESSAGE,
+ },
+ alertMessages: {
+ success_tag: DELETE_TAG_SUCCESS_MESSAGE,
+ danger_tag: DELETE_TAG_ERROR_MESSAGE,
+ success_tags: DELETE_TAGS_SUCCESS_MESSAGE,
+ danger_tags: DELETE_TAGS_ERROR_MESSAGE,
+ },
data() {
return {
selectedItems: [],
@@ -67,6 +92,7 @@ export default {
selectAllChecked: false,
modalDescription: null,
isDesktop: true,
+ deleteAlertType: false,
};
},
computed: {
@@ -110,20 +136,40 @@ export default {
this.requestTagsList({ pagination: { page }, params: this.$route.params.id });
},
},
+ deleteAlertConfig() {
+ const config = {
+ title: '',
+ message: '',
+ type: 'success',
+ };
+ if (this.deleteAlertType) {
+ [config.type] = this.deleteAlertType.split('_');
+
+ const defaultMessage = this.$options.alertMessages[this.deleteAlertType];
+
+ if (this.config.isAdmin && config.type === 'success') {
+ config.title = defaultMessage;
+ config.message = ADMIN_GARBAGE_COLLECTION_TIP;
+ } else {
+ config.message = defaultMessage;
+ }
+ }
+ return config;
+ },
},
methods: {
...mapActions(['requestTagsList', 'requestDeleteTag', 'requestDeleteTags']),
setModalDescription(itemIndex = -1) {
if (itemIndex === -1) {
this.modalDescription = {
- message: s__(`ContainerRegistry|You are about to remove %{item} tags. Are you sure?`),
+ message: REMOVE_TAGS_CONFIRMATION_TEXT,
item: this.itemsToBeDeleted.length,
};
} else {
const { path } = this.tags[itemIndex];
this.modalDescription = {
- message: s__(`ContainerRegistry|You are about to remove %{item}. Are you sure?`),
+ message: REMOVE_TAG_CONFIRMATION_TEXT,
item: path,
};
}
@@ -179,19 +225,17 @@ export default {
this.track('click_button');
this.$refs.deleteModal.show();
},
- handleSingleDelete(itemToDelete) {
+ handleSingleDelete(index) {
+ const itemToDelete = this.tags[index];
this.itemsToBeDeleted = [];
+ this.selectedItems = this.selectedItems.filter(i => i !== index);
return this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id })
- .then(() =>
- this.$toast.show(DELETE_TAG_SUCCESS_MESSAGE, {
- type: 'success',
- }),
- )
- .catch(() =>
- this.$toast.show(DELETE_TAG_ERROR_MESSAGE, {
- type: 'error',
- }),
- );
+ .then(() => {
+ this.deleteAlertType = 'success_tag';
+ })
+ .catch(() => {
+ this.deleteAlertType = 'danger_tag';
+ });
},
handleMultipleDelete() {
const { itemsToBeDeleted } = this;
@@ -202,24 +246,19 @@ export default {
ids: itemsToBeDeleted.map(x => this.tags[x].name),
params: this.$route.params.id,
})
- .then(() =>
- this.$toast.show(DELETE_TAGS_SUCCESS_MESSAGE, {
- type: 'success',
- }),
- )
- .catch(() =>
- this.$toast.show(DELETE_TAGS_ERROR_MESSAGE, {
- type: 'error',
- }),
- );
+ .then(() => {
+ this.deleteAlertType = 'success_tags';
+ })
+ .catch(() => {
+ this.deleteAlertType = 'danger_tags';
+ });
},
onDeletionConfirmed() {
this.track('confirm_delete');
if (this.isMultiDelete) {
this.handleMultipleDelete();
} else {
- const index = this.itemsToBeDeleted[0];
- this.handleSingleDelete(this.tags[index]);
+ this.handleSingleDelete(this.itemsToBeDeleted[0]);
}
},
handleResize() {
@@ -231,9 +270,24 @@ export default {
<template>
<div v-gl-resize-observer="handleResize" class="my-3 w-100 slide-enter-to-element">
+ <gl-alert
+ v-if="deleteAlertType"
+ :variant="deleteAlertConfig.type"
+ :title="deleteAlertConfig.title"
+ class="my-2"
+ @dismiss="deleteAlertType = null"
+ >
+ <gl-sprintf :message="deleteAlertConfig.message">
+ <template #docLink="{content}">
+ <gl-link :href="config.garbageCollectionHelpPagePath" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
<div class="d-flex my-3 align-items-center">
<h4>
- <gl-sprintf :message="s__('ContainerRegistry|%{imageName} tags')">
+ <gl-sprintf :message="$options.i18n.DETAILS_PAGE_TITLE">
<template #imageName>
{{ imageName }}
</template>
@@ -256,8 +310,8 @@ export default {
:disabled="!selectedItems || selectedItems.length === 0"
class="float-right"
variant="danger"
- :title="s__('ContainerRegistry|Remove selected tags')"
- :aria-label="s__('ContainerRegistry|Remove selected tags')"
+ :title="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
+ :aria-label="$options.i18n.REMOVE_TAGS_BUTTON_TITLE"
@click="deleteMultipleItems()"
>
<gl-icon name="remove" />
@@ -306,8 +360,8 @@ export default {
<template #cell(actions)="{index, item}">
<gl-deprecated-button
ref="singleDeleteButton"
- :title="s__('ContainerRegistry|Remove tag')"
- :aria-label="s__('ContainerRegistry|Remove tag')"
+ :title="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
+ :aria-label="$options.i18n.REMOVE_TAG_BUTTON_TITLE"
:disabled="!item.destroy_path"
variant="danger"
class="js-delete-registry float-right btn-inverted btn-border-color btn-icon"
@@ -337,15 +391,9 @@ export default {
</template>
<gl-empty-state
v-else
- :title="s__('ContainerRegistry|This image has no active tags')"
+ :title="$options.i18n.EMPTY_IMAGE_REPOSITORY_TITLE"
:svg-path="config.noContainersImage"
- :description="
- s__(
- `ContainerRegistry|The last tag related to this image was recently removed.
- This empty image and any associated data will be automatically removed as part of the regular Garbage Collection process.
- If you have any questions, contact your administrator.`,
- )
- "
+ :description="$options.i18n.EMPTY_IMAGE_REPOSITORY_MESSAGE"
class="mx-auto my-0"
/>
</template>
diff --git a/app/assets/javascripts/registry/explorer/pages/index.vue b/app/assets/javascripts/registry/explorer/pages/index.vue
index 95d83c82987..709a163d56d 100644
--- a/app/assets/javascripts/registry/explorer/pages/index.vue
+++ b/app/assets/javascripts/registry/explorer/pages/index.vue
@@ -1,46 +1,9 @@
<script>
-import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
-import { mapState, mapActions, mapGetters } from 'vuex';
-import { s__ } from '~/locale';
-
-export default {
- components: {
- GlAlert,
- GlSprintf,
- GlLink,
- },
- i18n: {
- garbageCollectionTipText: s__(
- 'ContainerRegistry|This Registry contains deleted image tag data. Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage.',
- ),
- },
- computed: {
- ...mapState(['config']),
- ...mapGetters(['showGarbageCollection']),
- },
- methods: {
- ...mapActions(['setShowGarbageCollectionTip']),
- },
-};
+export default {};
</script>
<template>
<div>
- <gl-alert
- v-if="showGarbageCollection"
- variant="tip"
- class="my-2"
- @dismiss="setShowGarbageCollectionTip(false)"
- >
- <gl-sprintf :message="$options.i18n.garbageCollectionTipText">
- <template #docLink="{content}">
- <gl-link :href="config.garbageCollectionHelpPagePath" target="_blank">
- {{ content }}
- </gl-link>
- </template>
- </gl-sprintf>
- </gl-alert>
-
<transition name="slide">
<router-view ref="router-view" />
</transition>
diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js
index b4f66dbbcd6..6e3cf3f0c80 100644
--- a/app/assets/javascripts/registry/explorer/stores/actions.js
+++ b/app/assets/javascripts/registry/explorer/stores/actions.js
@@ -66,7 +66,7 @@ export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) =
dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
- .catch(() => {
+ .finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
@@ -83,7 +83,7 @@ export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params })
dispatch('setShowGarbageCollectionTip', true);
return dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
- .catch(() => {
+ .finally(() => {
commit(types.SET_MAIN_LOADING, false);
});
};
diff --git a/app/models/group.rb b/app/models/group.rb
index d61705ca605..fd0f9cc223c 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -15,7 +15,6 @@ class Group < Namespace
include WithUploads
include Gitlab::Utils::StrongMemoize
include GroupAPICompatibility
- include HasWiki
ACCESS_REQUEST_APPROVERS_TO_BE_NOTIFIED_LIMIT = 10
diff --git a/app/models/group_wiki.rb b/app/models/group_wiki.rb
deleted file mode 100644
index 703ef80c121..00000000000
--- a/app/models/group_wiki.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-class GroupWiki < Wiki
- alias_method :group, :container
-
- override :storage
- def storage
- @storage ||= Storage::Hashed.new(container, prefix: Storage::Hashed::GROUP_REPOSITORY_PATH_PREFIX)
- end
-
- override :repository_storage
- def repository_storage
- # TODO: Add table to track storage
- # https://gitlab.com/gitlab-org/gitlab/-/issues/207865
- 'default'
- end
-
- override :hashed_storage?
- def hashed_storage?
- true
- end
-
- override :disk_path
- def disk_path(*args, &block)
- storage.disk_path + '.wiki'
- end
-end
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index e8987265a93..5f596ea7920 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -86,9 +86,9 @@
.content-block.emoji-block.emoji-block-sticky
.row
- .col-md-12.col-lg-6.js-noteable-awards
+ .col-md-12.col-lg-4.js-noteable-awards
= render 'award_emoji/awards_block', awardable: @issue, inline: true
- .col-md-12.col-lg-6.new-branch-col
+ .col-md-12.col-lg-8.new-branch-col
#js-vue-sort-issue-discussions
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(@issue), notes_filters: UserPreference.notes_filters.to_json } }
= render 'new_branch' if show_new_branch_button?
diff --git a/app/views/projects/static_site_editor/show.html.haml b/app/views/projects/static_site_editor/show.html.haml
index 88c5378fc35..8d2649be588 100644
--- a/app/views/projects/static_site_editor/show.html.haml
+++ b/app/views/projects/static_site_editor/show.html.haml
@@ -1,4 +1 @@
--# TODO: Remove after base URL is included in the model !30735
-- data = @config.payload.merge({ base_url: namespace_project_show_sse_path })
-
-#static-site-editor{ data: data }
+#static-site-editor{ data: @config.payload }
diff --git a/changelogs/unreleased/214477-reactor-toast-to-alert-in-image-repository-details-page.yml b/changelogs/unreleased/214477-reactor-toast-to-alert-in-image-repository-details-page.yml
new file mode 100644
index 00000000000..a87cb780b8c
--- /dev/null
+++ b/changelogs/unreleased/214477-reactor-toast-to-alert-in-image-repository-details-page.yml
@@ -0,0 +1,5 @@
+---
+title: Use alerts instead of toasts in Image Repository details
+merge_request: 29685
+author:
+type: changed
diff --git a/changelogs/unreleased/fix-issue-details-row-emoji-block.yml b/changelogs/unreleased/fix-issue-details-row-emoji-block.yml
new file mode 100644
index 00000000000..15d9025e3b4
--- /dev/null
+++ b/changelogs/unreleased/fix-issue-details-row-emoji-block.yml
@@ -0,0 +1,5 @@
+---
+title: Fix layout in issue view, on large screen some buttons were misaligned
+merge_request: 30947
+author: Michele (macno) Azzolari
+type: fixed
diff --git a/changelogs/unreleased/sh-revert-codeowners-check.yml b/changelogs/unreleased/sh-revert-codeowners-check.yml
new file mode 100644
index 00000000000..04181d0f678
--- /dev/null
+++ b/changelogs/unreleased/sh-revert-codeowners-check.yml
@@ -0,0 +1,5 @@
+---
+title: Revert CODEOWNERS validation of Web requests in diff check
+merge_request: 31087
+author:
+type: fixed
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 606f7c4d6b5..dd2008a2003 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -153,7 +153,13 @@ used by the `review-deploy` and `review-stop` jobs.
### Get access to the GCP Review Apps cluster
You need to [open an access request (internal link)](https://gitlab.com/gitlab-com/access-requests/issues/new)
-for the `gcp-review-apps-sg` GCP group.
+for the `gcp-review-apps-sg` GCP group. In order to join a group, you must specify the desired GCP role in your access request.
+The role is what will grant you specific permissions in order to engage with Review App containers.
+
+Here are some permissions you may want to have, and the roles that grant them:
+
+- `container.pods.getLogs` - Required to [retrieve pod logs](#dig-into-a-pods-logs). Granted by [Viewer (`roles/viewer`)](https://cloud.google.com/iam/docs/understanding-roles#kubernetes-engine-roles).
+- `container.pods.exec` - Required to [run a Rails console](#run-a-rails-console). Granted by [Kubernetes Engine Developer (`roles/container.developer`)](https://cloud.google.com/iam/docs/understanding-roles#kubernetes-engine-roles).
### Log into my Review App
@@ -175,7 +181,7 @@ secure note named `gitlab-{ce,ee} Review App's root password`.
### Run a Rails console
-1. Make sure you [have access to the cluster](#get-access-to-the-gcp-review-apps-cluster) first.
+1. Make sure you [have access to the cluster](#get-access-to-the-gcp-review-apps-cluster) and the `container.pods.exec` permission first.
1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps),
e.g. `review-qa-raise-e-12chm0`.
1. Find and open the `task-runner` Deployment, e.g. `review-qa-raise-e-12chm0-task-runner`.
@@ -191,7 +197,7 @@ secure note named `gitlab-{ce,ee} Review App's root password`.
### Dig into a Pod's logs
-1. Make sure you [have access to the cluster](#get-access-to-the-gcp-review-apps-cluster) first.
+1. Make sure you [have access to the cluster](#get-access-to-the-gcp-review-apps-cluster) and the `container.pods.getLogs` permission first.
1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps),
e.g. `review-qa-raise-e-12chm0`.
1. Find and open the `migrations` Deployment, e.g.
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index c38769f39a9..5cd2a86a106 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -34,14 +34,16 @@ module Gitlab
def init_metrics
metrics = {
- file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels),
- memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels),
- process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
- process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
- process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used', labels),
- process_start_time_seconds: ::Gitlab::Metrics.gauge(with_prefix(:process, :start_time_seconds), 'Process start time seconds'),
- sampler_duration: ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels),
- gc_duration_seconds: ::Gitlab::Metrics.histogram(with_prefix(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS)
+ file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels),
+ memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used (RSS)', labels),
+ process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
+ process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
+ process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used (RSS)', labels),
+ process_unique_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :unique_memory_bytes), 'Memory used (USS)', labels),
+ process_proportional_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :proportional_memory_bytes), 'Memory used (PSS)', labels),
+ process_start_time_seconds: ::Gitlab::Metrics.gauge(with_prefix(:process, :start_time_seconds), 'Process start time seconds'),
+ sampler_duration: ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels),
+ gc_duration_seconds: ::Gitlab::Metrics.histogram(with_prefix(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS)
}
GC.stat.keys.each do |key|
@@ -85,10 +87,15 @@ module Gitlab
end
def set_memory_usage_metrics
- memory_usage = System.memory_usage
-
- metrics[:memory_bytes].set(labels, memory_usage)
- metrics[:process_resident_memory_bytes].set(labels, memory_usage)
+ memory_rss = System.memory_usage
+ metrics[:memory_bytes].set(labels, memory_rss)
+ metrics[:process_resident_memory_bytes].set(labels, memory_rss)
+
+ if Feature.enabled?(:collect_memory_uss_pss)
+ memory_uss_pss = System.memory_usage_uss_pss
+ metrics[:process_unique_memory_bytes].set(labels, memory_uss_pss[:uss])
+ metrics[:process_proportional_memory_bytes].set(labels, memory_uss_pss[:pss])
+ end
end
end
end
diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb
index 2a61b3de405..d01b6bc5b50 100644
--- a/lib/gitlab/metrics/system.rb
+++ b/lib/gitlab/metrics/system.rb
@@ -7,47 +7,37 @@ module Gitlab
# This module relies on the /proc filesystem being available. If /proc is
# not available the methods of this module will be stubbed.
module System
- if File.exist?('/proc')
- # Returns the current process' memory usage in bytes.
- def self.memory_usage
- mem = 0
- match = File.read('/proc/self/status').match(/VmRSS:\s+(\d+)/)
-
- if match && match[1]
- mem = match[1].to_f * 1024
- end
-
- mem
- end
-
- def self.file_descriptor_count
- Dir.glob('/proc/self/fd/*').length
- end
-
- def self.max_open_file_descriptors
- match = File.read('/proc/self/limits').match(/Max open files\s*(\d+)/)
-
- return unless match && match[1]
+ PROC_STATUS_PATH = '/proc/self/status'
+ PROC_SMAPS_ROLLUP_PATH = '/proc/self/smaps_rollup'
+ PROC_LIMITS_PATH = '/proc/self/limits'
+ PROC_FD_GLOB = '/proc/self/fd/*'
+
+ PRIVATE_PAGES_PATTERN = /^(Private_Clean|Private_Dirty|Private_Hugetlb):\s+(?<value>\d+)/.freeze
+ PSS_PATTERN = /^Pss:\s+(?<value>\d+)/.freeze
+ RSS_PATTERN = /VmRSS:\s+(?<value>\d+)/.freeze
+ MAX_OPEN_FILES_PATTERN = /Max open files\s*(?<value>\d+)/.freeze
+
+ # Returns the current process' RSS (resident set size) in bytes.
+ def self.memory_usage
+ sum_matches(PROC_STATUS_PATH, rss: RSS_PATTERN)[:rss].kilobytes
+ end
- match[1].to_i
- end
- else
- def self.memory_usage
- 0.0
- end
+ # Returns the current process' USS/PSS (unique/proportional set size) in bytes.
+ def self.memory_usage_uss_pss
+ sum_matches(PROC_SMAPS_ROLLUP_PATH, uss: PRIVATE_PAGES_PATTERN, pss: PSS_PATTERN)
+ .transform_values(&:kilobytes)
+ end
- def self.file_descriptor_count
- 0
- end
+ def self.file_descriptor_count
+ Dir.glob(PROC_FD_GLOB).length
+ end
- def self.max_open_file_descriptors
- 0
- end
+ def self.max_open_file_descriptors
+ sum_matches(PROC_LIMITS_PATH, max_fds: MAX_OPEN_FILES_PATTERN)[:max_fds]
end
def self.cpu_time
- Process
- .clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
+ Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
end
# Returns the current real time in a given precision.
@@ -78,6 +68,27 @@ module Gitlab
end_time - start_time
end
+
+ # Given a path to a file in /proc and a hash of (metric, pattern) pairs,
+ # sums up all values found for those patterns under the respective metric.
+ def self.sum_matches(proc_file, **patterns)
+ results = patterns.transform_values { 0 }
+
+ begin
+ File.foreach(proc_file) do |line|
+ patterns.each do |metric, pattern|
+ match = line.match(pattern)
+ value = match&.named_captures&.fetch('value', 0)
+ results[metric] += value.to_i
+ end
+ end
+ rescue Errno::ENOENT
+ # This means the procfile we're reading from did not exist;
+ # this is safe to ignore, since we initialize each metric to 0
+ end
+
+ results
+ end
end
end
end
diff --git a/lib/gitlab/static_site_editor/config.rb b/lib/gitlab/static_site_editor/config.rb
index 1862d949817..c931cdecbeb 100644
--- a/lib/gitlab/static_site_editor/config.rb
+++ b/lib/gitlab/static_site_editor/config.rb
@@ -22,7 +22,8 @@ module Gitlab
project: project.path,
namespace: project.namespace.path,
return_url: return_url,
- is_supported_content: supported_content?.to_s
+ is_supported_content: supported_content?.to_s,
+ base_url: Gitlab::Routing.url_helpers.project_show_sse_path(project, full_path)
}
end
@@ -47,6 +48,10 @@ module Gitlab
def file_exists?
commit_id.present? && repository.blob_at(commit_id, file_path).present?
end
+
+ def full_path
+ "#{ref}/#{file_path}"
+ end
end
end
end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index 80e31b06ba2..d292b36b311 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -72,13 +72,8 @@ module Gitlab
end
def wiki_url(object, **options)
- case object.container
- when Project
+ if object.container.is_a?(Project)
instance.project_wiki_url(object.container, Wiki::HOMEPAGE, **options)
- when Group
- # TODO: Use the new route for group wikis once we add it.
- # https://gitlab.com/gitlab-org/gitlab/-/issues/211360
- instance.group_canonical_url(object.container, **options) + "/-/wikis/#{Wiki::HOMEPAGE}"
else
raise NotImplementedError.new("No URL builder defined for #{object.inspect}")
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index f42f139b30d..0ae16d93a6e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5713,6 +5713,9 @@ msgstr ""
msgid "ContainerRegistry|Regular expressions such as %{codeStart}.*-test%{codeEnd} or %{codeStart}dev-.*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}"
msgstr ""
+msgid "ContainerRegistry|Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
+msgstr ""
+
msgid "ContainerRegistry|Remove repository"
msgstr ""
@@ -5727,19 +5730,19 @@ msgstr[1] ""
msgid "ContainerRegistry|Retention policy has been Enabled"
msgstr ""
-msgid "ContainerRegistry|Something went wrong while deleting the tag."
+msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
msgstr ""
-msgid "ContainerRegistry|Something went wrong while deleting the tags."
+msgid "ContainerRegistry|Something went wrong while fetching the repository list."
msgstr ""
-msgid "ContainerRegistry|Something went wrong while fetching the expiration policy."
+msgid "ContainerRegistry|Something went wrong while fetching the tags list."
msgstr ""
-msgid "ContainerRegistry|Something went wrong while fetching the repository list."
+msgid "ContainerRegistry|Something went wrong while marking the tag for deletion."
msgstr ""
-msgid "ContainerRegistry|Something went wrong while fetching the tags list."
+msgid "ContainerRegistry|Something went wrong while marking the tags for deletion."
msgstr ""
msgid "ContainerRegistry|Something went wrong while scheduling %{title} for deletion. Please try again."
@@ -5751,16 +5754,16 @@ msgstr ""
msgid "ContainerRegistry|Tag"
msgstr ""
-msgid "ContainerRegistry|Tag deleted successfully"
-msgstr ""
-
msgid "ContainerRegistry|Tag expiration policy"
msgstr ""
msgid "ContainerRegistry|Tag expiration policy is designed to:"
msgstr ""
-msgid "ContainerRegistry|Tags deleted successfully"
+msgid "ContainerRegistry|Tag successfully marked for deletion."
+msgstr ""
+
+msgid "ContainerRegistry|Tags successfully marked for deletion."
msgstr ""
msgid "ContainerRegistry|Tags with names matching this regex pattern will %{italicStart}be preserved:%{italicEnd}"
@@ -5793,9 +5796,6 @@ msgstr ""
msgid "ContainerRegistry|There was an error during the deletion of this image repository, please try again."
msgstr ""
-msgid "ContainerRegistry|This Registry contains deleted image tag data. Remember to run %{docLinkStart}garbage collection%{docLinkEnd} to remove the stale data from storage."
-msgstr ""
-
msgid "ContainerRegistry|This image has no active tags"
msgstr ""
diff --git a/package.json b/package.json
index c750481cd84..b5b772601eb 100644
--- a/package.json
+++ b/package.json
@@ -39,7 +39,7 @@
"@babel/plugin-syntax-import-meta": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@gitlab/at.js": "1.5.5",
- "@gitlab/svgs": "1.123.0",
+ "@gitlab/svgs": "1.125.0",
"@gitlab/ui": "13.9.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.2-2",
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 6d292d529ab..4b6c1756d1e 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -51,11 +51,5 @@ FactoryBot.define do
trait :owner_subgroup_creation_only do
subgroup_creation_level { ::Gitlab::Access::OWNER_SUBGROUP_ACCESS}
end
-
- trait :wiki_repo do
- after(:create) do |group|
- raise 'Failed to create wiki repository!' unless group.create_wiki
- end
- end
end
end
diff --git a/spec/factories/wikis.rb b/spec/factories/wikis.rb
index b591b0f169d..96578fdcee6 100644
--- a/spec/factories/wikis.rb
+++ b/spec/factories/wikis.rb
@@ -17,9 +17,5 @@ FactoryBot.define do
container { project }
end
-
- factory :group_wiki do
- container { association(:group, :wiki_repo) }
- end
end
end
diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb
index df15f995be4..5c7c83aea6d 100644
--- a/spec/features/groups/members/leave_group_spec.rb
+++ b/spec/features/groups/members/leave_group_spec.rb
@@ -31,8 +31,6 @@ describe 'Groups > Members > Leave group' do
page.accept_confirm
- expect(find('.flash-notice')).to have_content "You left the \"#{group.full_name}\" group"
- expect(page).to have_content left_group_message(group)
expect(current_path).to eq(dashboard_groups_path)
expect(group.users).not_to include(user)
end
diff --git a/spec/frontend/registry/explorer/pages/details_spec.js b/spec/frontend/registry/explorer/pages/details_spec.js
index 15aa5008413..d6741aa3141 100644
--- a/spec/frontend/registry/explorer/pages/details_spec.js
+++ b/spec/frontend/registry/explorer/pages/details_spec.js
@@ -1,15 +1,16 @@
import { mount } from '@vue/test-utils';
-import { GlTable, GlPagination, GlSkeletonLoader } from '@gitlab/ui';
+import { GlTable, GlPagination, GlSkeletonLoader, GlAlert, GlLink } from '@gitlab/ui';
import Tracking from '~/tracking';
import stubChildren from 'helpers/stub_children';
import component from '~/registry/explorer/pages/details.vue';
-import store from '~/registry/explorer/stores/';
-import { SET_MAIN_LOADING } from '~/registry/explorer/stores/mutation_types/';
+import { createStore } from '~/registry/explorer/stores/';
+import { SET_MAIN_LOADING, SET_INITIAL_STATE } from '~/registry/explorer/stores/mutation_types/';
import {
DELETE_TAG_SUCCESS_MESSAGE,
DELETE_TAG_ERROR_MESSAGE,
DELETE_TAGS_SUCCESS_MESSAGE,
DELETE_TAGS_ERROR_MESSAGE,
+ ADMIN_GARBAGE_COLLECTION_TIP,
} from '~/registry/explorer/constants';
import { tagsListResponse } from '../mock_data';
import { GlModal } from '../stubs';
@@ -18,6 +19,7 @@ import { $toast } from '../../shared/mocks';
describe('Details Page', () => {
let wrapper;
let dispatchSpy;
+ let store;
const findDeleteModal = () => wrapper.find(GlModal);
const findPagination = () => wrapper.find(GlPagination);
@@ -30,6 +32,7 @@ describe('Details Page', () => {
const findAllCheckboxes = () => wrapper.findAll('.js-row-checkbox');
const findCheckedCheckboxes = () => findAllCheckboxes().filter(c => c.attributes('checked'));
const findFirsTagColumn = () => wrapper.find('.js-tag-column');
+ const findAlert = () => wrapper.find(GlAlert);
const routeId = window.btoa(JSON.stringify({ name: 'foo', tags_path: 'bar' }));
@@ -55,6 +58,7 @@ describe('Details Page', () => {
};
beforeEach(() => {
+ store = createStore();
dispatchSpy = jest.spyOn(store, 'dispatch');
store.dispatch('receiveTagsListSuccess', tagsListResponse);
jest.spyOn(Tracking, 'event');
@@ -62,6 +66,7 @@ describe('Details Page', () => {
afterEach(() => {
wrapper.destroy();
+ wrapper = null;
});
describe('when isLoading is true', () => {
@@ -328,25 +333,9 @@ describe('Details Page', () => {
});
// itemsToBeDeleted is not represented in the DOM, is used as parking variable between selected and deleted items
expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
+ expect(wrapper.vm.selectedItems).toEqual([]);
expect(findCheckedCheckboxes()).toHaveLength(0);
});
-
- it('show success toast on successful delete', () => {
- return wrapper.vm.handleSingleDelete(0).then(() => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAG_SUCCESS_MESSAGE, {
- type: 'success',
- });
- });
- });
-
- it('show error toast on erred delete', () => {
- dispatchSpy.mockRejectedValue();
- return wrapper.vm.handleSingleDelete(0).then(() => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAG_ERROR_MESSAGE, {
- type: 'error',
- });
- });
- });
});
describe('when multiple elements are selected', () => {
@@ -365,23 +354,6 @@ describe('Details Page', () => {
expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
expect(findCheckedCheckboxes()).toHaveLength(0);
});
-
- it('show success toast on successful delete', () => {
- return wrapper.vm.handleMultipleDelete(0).then(() => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_SUCCESS_MESSAGE, {
- type: 'success',
- });
- });
- });
-
- it('show error toast on erred delete', () => {
- dispatchSpy.mockRejectedValue();
- return wrapper.vm.handleMultipleDelete(0).then(() => {
- expect(wrapper.vm.$toast.show).toHaveBeenCalledWith(DELETE_TAGS_ERROR_MESSAGE, {
- type: 'error',
- });
- });
- });
});
});
@@ -395,4 +367,108 @@ describe('Details Page', () => {
});
});
});
+
+ describe('Delete alert', () => {
+ const config = {
+ garbageCollectionHelpPagePath: 'foo',
+ };
+
+ describe('when the user is an admin', () => {
+ beforeEach(() => {
+ store.commit(SET_INITIAL_STATE, { ...config, isAdmin: true });
+ });
+
+ afterEach(() => {
+ store.commit(SET_INITIAL_STATE, config);
+ });
+
+ describe.each`
+ deleteType | successTitle | errorTitle
+ ${'handleSingleDelete'} | ${DELETE_TAG_SUCCESS_MESSAGE} | ${DELETE_TAG_ERROR_MESSAGE}
+ ${'handleMultipleDelete'} | ${DELETE_TAGS_SUCCESS_MESSAGE} | ${DELETE_TAGS_ERROR_MESSAGE}
+ `('behaves correctly on $deleteType', ({ deleteType, successTitle, errorTitle }) => {
+ describe('when delete is successful', () => {
+ beforeEach(() => {
+ dispatchSpy.mockResolvedValue();
+ mountComponent();
+ return wrapper.vm[deleteType]('foo');
+ });
+
+ it('alert exists', () => {
+ expect(findAlert().exists()).toBe(true);
+ });
+
+ it('alert body contains admin tip', () => {
+ expect(
+ findAlert()
+ .text()
+ .replace(/\s\s+/gm, ' '),
+ ).toBe(ADMIN_GARBAGE_COLLECTION_TIP.replace(/%{\w+}/gm, ''));
+ });
+
+ it('alert body contains link', () => {
+ const alertLink = findAlert().find(GlLink);
+ expect(alertLink.exists()).toBe(true);
+ expect(alertLink.attributes('href')).toBe(config.garbageCollectionHelpPagePath);
+ });
+
+ it('alert title is appropriate', () => {
+ expect(findAlert().attributes('title')).toBe(successTitle);
+ });
+ });
+
+ describe('when delete is not successful', () => {
+ beforeEach(() => {
+ mountComponent();
+ dispatchSpy.mockRejectedValue();
+ return wrapper.vm[deleteType]('foo');
+ });
+
+ it('alert exist and text is appropriate', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(errorTitle);
+ });
+ });
+ });
+ });
+
+ describe.each`
+ deleteType | successTitle | errorTitle
+ ${'handleSingleDelete'} | ${DELETE_TAG_SUCCESS_MESSAGE} | ${DELETE_TAG_ERROR_MESSAGE}
+ ${'handleMultipleDelete'} | ${DELETE_TAGS_SUCCESS_MESSAGE} | ${DELETE_TAGS_ERROR_MESSAGE}
+ `(
+ 'when the user is not an admin alert behaves correctly on $deleteType',
+ ({ deleteType, successTitle, errorTitle }) => {
+ beforeEach(() => {
+ store.commit('SET_INITIAL_STATE', { ...config });
+ });
+
+ describe('when delete is successful', () => {
+ beforeEach(() => {
+ dispatchSpy.mockResolvedValue();
+ mountComponent();
+ return wrapper.vm[deleteType]('foo');
+ });
+
+ it('alert exist and text is appropriate', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(successTitle);
+ });
+ });
+
+ describe('when delete is not successful', () => {
+ beforeEach(() => {
+ mountComponent();
+ dispatchSpy.mockRejectedValue();
+ return wrapper.vm[deleteType]('foo');
+ });
+
+ it('alert exist and text is appropriate', () => {
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(errorTitle);
+ });
+ });
+ },
+ );
+ });
});
diff --git a/spec/frontend/registry/explorer/pages/index_spec.js b/spec/frontend/registry/explorer/pages/index_spec.js
index f52e7d67866..b558727ed5e 100644
--- a/spec/frontend/registry/explorer/pages/index_spec.js
+++ b/spec/frontend/registry/explorer/pages/index_spec.js
@@ -1,62 +1,26 @@
import { shallowMount } from '@vue/test-utils';
-import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
import component from '~/registry/explorer/pages/index.vue';
import store from '~/registry/explorer/stores/';
describe('List Page', () => {
let wrapper;
- let dispatchSpy;
const findRouterView = () => wrapper.find({ ref: 'router-view' });
- const findAlert = () => wrapper.find(GlAlert);
- const findLink = () => wrapper.find(GlLink);
const mountComponent = () => {
wrapper = shallowMount(component, {
store,
stubs: {
RouterView: true,
- GlSprintf,
},
});
};
beforeEach(() => {
- dispatchSpy = jest.spyOn(store, 'dispatch');
mountComponent();
});
it('has a router view', () => {
expect(findRouterView().exists()).toBe(true);
});
-
- describe('garbageCollectionTip alert', () => {
- beforeEach(() => {
- store.dispatch('setInitialState', { isAdmin: true, garbageCollectionHelpPagePath: 'foo' });
- store.dispatch('setShowGarbageCollectionTip', true);
- });
-
- afterEach(() => {
- store.dispatch('setInitialState', {});
- store.dispatch('setShowGarbageCollectionTip', false);
- });
-
- it('is visible when the user is an admin and the user performed a delete action', () => {
- expect(findAlert().exists()).toBe(true);
- });
-
- it('on dismiss disappears ', () => {
- findAlert().vm.$emit('dismiss');
- expect(dispatchSpy).toHaveBeenCalledWith('setShowGarbageCollectionTip', false);
- return wrapper.vm.$nextTick().then(() => {
- expect(findAlert().exists()).toBe(false);
- });
- });
-
- it('contains a link to the docs', () => {
- const link = findLink();
- expect(link.exists()).toBe(true);
- expect(link.attributes('href')).toBe(store.state.config.garbageCollectionHelpPagePath);
- });
- });
});
diff --git a/spec/frontend/registry/explorer/stores/actions_spec.js b/spec/frontend/registry/explorer/stores/actions_spec.js
index 58f61a0e8c2..15f9db90910 100644
--- a/spec/frontend/registry/explorer/stores/actions_spec.js
+++ b/spec/frontend/registry/explorer/stores/actions_spec.js
@@ -191,7 +191,10 @@ describe('Actions RegistryExplorer Store', () => {
{
tagsPagination: {},
},
- [{ type: types.SET_MAIN_LOADING, payload: true }],
+ [
+ { type: types.SET_MAIN_LOADING, payload: true },
+ { type: types.SET_MAIN_LOADING, payload: false },
+ ],
[
{
type: 'setShowGarbageCollectionTip',
@@ -220,8 +223,7 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
- done,
- );
+ ).catch(() => done());
});
});
@@ -241,7 +243,10 @@ describe('Actions RegistryExplorer Store', () => {
{
tagsPagination: {},
},
- [{ type: types.SET_MAIN_LOADING, payload: true }],
+ [
+ { type: types.SET_MAIN_LOADING, payload: true },
+ { type: types.SET_MAIN_LOADING, payload: false },
+ ],
[
{
type: 'setShowGarbageCollectionTip',
@@ -273,8 +278,7 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
- done,
- );
+ ).catch(() => done());
});
});
@@ -311,9 +315,7 @@ describe('Actions RegistryExplorer Store', () => {
{ type: types.SET_MAIN_LOADING, payload: false },
],
[],
- ).catch(() => {
- done();
- });
+ ).catch(() => done());
});
});
});
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 8c4071a7ed1..9d8ec2d9b21 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -19,20 +19,19 @@ describe Gitlab::Metrics::Samplers::RubySampler do
end
describe '#sample' do
- it 'samples various statistics' do
- expect(Gitlab::Metrics::System).to receive(:cpu_time)
- expect(Gitlab::Metrics::System).to receive(:file_descriptor_count)
- expect(Gitlab::Metrics::System).to receive(:memory_usage)
- expect(Gitlab::Metrics::System).to receive(:max_open_file_descriptors)
- expect(sampler).to receive(:sample_gc)
+ it 'adds a metric containing the process resident memory bytes' do
+ expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
+
+ expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000)
sampler.sample
end
- it 'adds a metric containing the process resident memory bytes' do
- expect(Gitlab::Metrics::System).to receive(:memory_usage).and_return(9000)
+ it 'adds a metric containing the process unique and proportional memory bytes' do
+ expect(Gitlab::Metrics::System).to receive(:memory_usage_uss_pss).and_return(uss: 9000, pss: 10_000)
- expect(sampler.metrics[:process_resident_memory_bytes]).to receive(:set).with({}, 9000)
+ expect(sampler.metrics[:process_unique_memory_bytes]).to receive(:set).with({}, 9000)
+ expect(sampler.metrics[:process_proportional_memory_bytes]).to receive(:set).with({}, 10_000)
sampler.sample
end
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index a5aa80686fd..37d26bd9d63 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -3,33 +3,122 @@
require 'spec_helper'
describe Gitlab::Metrics::System do
- if File.exist?('/proc')
+ context 'when /proc files exist' do
+ # Fixtures pulled from:
+ # Linux carbon 5.3.0-7648-generic #41~1586789791~19.10~9593806-Ubuntu SMP Mon Apr 13 17:50:40 UTC x86_64 x86_64 x86_64 GNU/Linux
+ let(:proc_status) do
+ # most rows omitted for brevity
+ <<~SNIP
+ Name: less
+ VmHWM: 2468 kB
+ VmRSS: 2468 kB
+ RssAnon: 260 kB
+ SNIP
+ end
+
+ let(:proc_smaps_rollup) do
+ # full snapshot
+ <<~SNIP
+ Rss: 2564 kB
+ Pss: 503 kB
+ Pss_Anon: 312 kB
+ Pss_File: 191 kB
+ Pss_Shmem: 0 kB
+ Shared_Clean: 2100 kB
+ Shared_Dirty: 0 kB
+ Private_Clean: 152 kB
+ Private_Dirty: 312 kB
+ Referenced: 2564 kB
+ Anonymous: 312 kB
+ LazyFree: 0 kB
+ AnonHugePages: 0 kB
+ ShmemPmdMapped: 0 kB
+ Shared_Hugetlb: 0 kB
+ Private_Hugetlb: 0 kB
+ Swap: 0 kB
+ SwapPss: 0 kB
+ Locked: 0 kB
+ SNIP
+ end
+
+ let(:proc_limits) do
+ # full snapshot
+ <<~SNIP
+ Limit Soft Limit Hard Limit Units
+ Max cpu time unlimited unlimited seconds
+ Max file size unlimited unlimited bytes
+ Max data size unlimited unlimited bytes
+ Max stack size 8388608 unlimited bytes
+ Max core file size 0 unlimited bytes
+ Max resident set unlimited unlimited bytes
+ Max processes 126519 126519 processes
+ Max open files 1024 1048576 files
+ Max locked memory 67108864 67108864 bytes
+ Max address space unlimited unlimited bytes
+ Max file locks unlimited unlimited locks
+ Max pending signals 126519 126519 signals
+ Max msgqueue size 819200 819200 bytes
+ Max nice priority 0 0
+ Max realtime priority 0 0
+ Max realtime timeout unlimited unlimited us
+ SNIP
+ end
+
describe '.memory_usage' do
- it "returns the process' memory usage in bytes" do
- expect(described_class.memory_usage).to be > 0
+ it "returns the process' resident set size (RSS) in bytes" do
+ mock_existing_proc_file('/proc/self/status', proc_status)
+
+ expect(described_class.memory_usage).to eq(2527232)
end
end
describe '.file_descriptor_count' do
it 'returns the amount of open file descriptors' do
- expect(described_class.file_descriptor_count).to be > 0
+ expect(Dir).to receive(:glob).and_return(['/some/path', '/some/other/path'])
+
+ expect(described_class.file_descriptor_count).to eq(2)
end
end
describe '.max_open_file_descriptors' do
it 'returns the max allowed open file descriptors' do
- expect(described_class.max_open_file_descriptors).to be > 0
+ mock_existing_proc_file('/proc/self/limits', proc_limits)
+
+ expect(described_class.max_open_file_descriptors).to eq(1024)
+ end
+ end
+
+ describe '.memory_usage_uss_pss' do
+ it "returns the process' unique and porportional set size (USS/PSS) in bytes" do
+ mock_existing_proc_file('/proc/self/smaps_rollup', proc_smaps_rollup)
+
+ # (Private_Clean (152 kB) + Private_Dirty (312 kB) + Private_Hugetlb (0 kB)) * 1024
+ expect(described_class.memory_usage_uss_pss).to eq(uss: 475136, pss: 515072)
end
end
- else
+ end
+
+ context 'when /proc files do not exist' do
+ before do
+ mock_missing_proc_file
+ end
+
describe '.memory_usage' do
- it 'returns 0.0' do
- expect(described_class.memory_usage).to eq(0.0)
+ it 'returns 0' do
+ expect(described_class.memory_usage).to eq(0)
+ end
+ end
+
+ describe '.memory_usage_uss_pss' do
+ it "returns 0 for all components" do
+ expect(described_class.memory_usage_uss_pss).to eq(uss: 0, pss: 0)
end
end
describe '.file_descriptor_count' do
it 'returns 0' do
+ expect(Dir).to receive(:glob).and_return([])
+
expect(described_class.file_descriptor_count).to eq(0)
end
end
@@ -98,4 +187,12 @@ describe Gitlab::Metrics::System do
expect(described_class.thread_cpu_duration(start_time)).to be_nil
end
end
+
+ def mock_existing_proc_file(path, content)
+ allow(File).to receive(:foreach).with(path) { |_path, &block| content.each_line(&block) }
+ end
+
+ def mock_missing_proc_file
+ allow(File).to receive(:foreach).and_raise(Errno::ENOENT)
+ end
end
diff --git a/spec/lib/gitlab/repository_url_builder_spec.rb b/spec/lib/gitlab/repository_url_builder_spec.rb
index 1eb9de48405..a5797146cc5 100644
--- a/spec/lib/gitlab/repository_url_builder_spec.rb
+++ b/spec/lib/gitlab/repository_url_builder_spec.rb
@@ -10,7 +10,6 @@ describe Gitlab::RepositoryUrlBuilder do
:project | ->(project) { project.full_path }
:project_snippet | ->(snippet) { "#{snippet.project.full_path}/snippets/#{snippet.id}" }
:project_wiki | ->(wiki) { "#{wiki.container.full_path}.wiki" }
- :group_wiki | ->(wiki) { "#{wiki.container.full_path}.wiki" }
:personal_snippet | ->(snippet) { "snippets/#{snippet.id}" }
end
diff --git a/spec/lib/gitlab/static_site_editor/config_spec.rb b/spec/lib/gitlab/static_site_editor/config_spec.rb
index b32af912ad9..a1db567db1a 100644
--- a/spec/lib/gitlab/static_site_editor/config_spec.rb
+++ b/spec/lib/gitlab/static_site_editor/config_spec.rb
@@ -5,9 +5,10 @@ require 'spec_helper'
describe Gitlab::StaticSiteEditor::Config do
subject(:config) { described_class.new(repository, ref, file_path, return_url) }
- let(:project) { create(:project, :public, :repository, name: 'project', namespace: namespace) }
- let(:namespace) { create(:namespace, name: 'namespace') }
- let(:repository) { project.repository }
+ let_it_be(:namespace) { create(:namespace, name: 'namespace') }
+ let_it_be(:project) { create(:project, :public, :repository, name: 'project', namespace: namespace) }
+ let_it_be(:repository) { project.repository }
+
let(:ref) { 'master' }
let(:file_path) { 'README.md' }
let(:return_url) { 'http://example.com' }
@@ -24,10 +25,17 @@ describe Gitlab::StaticSiteEditor::Config do
project: 'project',
project_id: project.id,
return_url: 'http://example.com',
- is_supported_content: 'true'
+ is_supported_content: 'true',
+ base_url: '/namespace/project/-/sse/master%2FREADME.md'
)
end
+ context 'when file path is nested' do
+ let(:file_path) { 'lib/README.md' }
+
+ it { is_expected.to include(base_url: '/namespace/project/-/sse/master%2Flib%2FREADME.md') }
+ end
+
context 'when branch is not master' do
let(:ref) { 'my-branch' }
@@ -53,7 +61,7 @@ describe Gitlab::StaticSiteEditor::Config do
end
context 'when repository is empty' do
- let(:project) { create(:project_empty_repo) }
+ let(:repository) { create(:project_empty_repo).repository }
it { is_expected.to include(is_supported_content: 'false') }
end
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 7989b863781..71ffa4a00a1 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -28,7 +28,6 @@ describe Gitlab::UrlBuilder do
:group | ->(group) { "/groups/#{group.full_path}" }
:group_milestone | ->(milestone) { "/groups/#{milestone.group.full_path}/-/milestones/#{milestone.iid}" }
- :group_wiki | ->(wiki) { "/groups/#{wiki.container.full_path}/-/wikis/home" }
:user | ->(user) { "/#{user.full_path}" }
:personal_snippet | ->(snippet) { "/snippets/#{snippet.id}" }
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 04801983d6c..6e2cc10ab0c 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -27,11 +27,6 @@ describe Group do
it { is_expected.to have_many(:milestones) }
it { is_expected.to have_many(:sprints) }
- it_behaves_like 'model with wiki' do
- let(:container) { create(:group, :nested, :wiki_repo) }
- let(:container_without_wiki) { create(:group, :nested) }
- end
-
describe '#members & #requesters' do
let(:requester) { create(:user) }
let(:developer) { create(:user) }
diff --git a/spec/models/group_wiki_spec.rb b/spec/models/group_wiki_spec.rb
deleted file mode 100644
index ed6bac5311b..00000000000
--- a/spec/models/group_wiki_spec.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe GroupWiki do
- it_behaves_like 'wiki model' do
- let(:wiki_container) { create(:group, :wiki_repo) }
- let(:wiki_container_without_repo) { create(:group) }
-
- before do
- wiki_container.add_owner(user)
- end
-
- describe '#storage' do
- it 'uses the group repository prefix' do
- expect(subject.storage.base_dir).to start_with('@groups/')
- end
- end
-
- describe '#repository_storage' do
- it 'returns the default storage' do
- expect(subject.repository_storage).to eq('default')
- end
- end
-
- describe '#hashed_storage?' do
- it 'returns true' do
- expect(subject.hashed_storage?).to be(true)
- end
- end
-
- describe '#disk_path' do
- it 'returns the repository storage path' do
- expect(subject.disk_path).to eq("#{subject.storage.disk_path}.wiki")
- end
- end
- end
-end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 99a2ce28d19..eb241fa123f 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -150,15 +150,7 @@ describe WikiPage do
enable_front_matter_for(container)
end
- context 'with a project container' do
- it_behaves_like 'a page with front-matter'
- end
-
- context 'with a group container' do
- let(:container) { create(:group) }
-
- it_behaves_like 'a page with front-matter'
- end
+ it_behaves_like 'a page with front-matter'
end
end
end
@@ -512,15 +504,7 @@ describe WikiPage do
enable_front_matter_for(container)
end
- context 'with a project container' do
- it_behaves_like 'able to update front-matter'
- end
-
- context 'with a group container' do
- let(:container) { create(:group) }
-
- it_behaves_like 'able to update front-matter'
- end
+ it_behaves_like 'able to update front-matter'
end
end
@@ -826,22 +810,13 @@ describe WikiPage do
expect(subject).not_to eq(other_page)
end
- it 'returns false for page with the same slug on a different container of the same type' do
+ it 'returns false for page with the same slug on a different container' do
other_page = create(:wiki_page, title: existing_page.slug)
expect(subject.slug).to eq(other_page.slug)
expect(subject.container).not_to eq(other_page.container)
expect(subject).not_to eq(other_page)
end
-
- it 'returns false for page with the same slug on a different container type' do
- group = create(:group, name: container.name)
- other_page = create(:wiki_page, title: existing_page.slug, container: group)
-
- expect(subject.slug).to eq(other_page.slug)
- expect(subject.container).not_to eq(other_page.container)
- expect(subject).not_to eq(other_page)
- end
end
describe '#last_commit_sha' do
diff --git a/yarn.lock b/yarn.lock
index 33f232fe284..0a4ef63d580 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -782,10 +782,10 @@
eslint-plugin-vue "^6.2.1"
vue-eslint-parser "^7.0.0"
-"@gitlab/svgs@1.123.0":
- version "1.123.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.123.0.tgz#465946ae7afc486d6769dc38685f71747fa2fec7"
- integrity sha512-lBTNnh7sEgUX3LVj6tEis9dcDDc5gKhCSUInGzswZVy9KeDAXbY850pKGPRKg/O1nVDPIe9yh7ieieWy25bkuQ==
+"@gitlab/svgs@1.125.0":
+ version "1.125.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.125.0.tgz#59c667dae8f7e4c80b482f5f6cc35367c016387b"
+ integrity sha512-MKfFYa8f+9P2tJ/JN/E9oDBSSo/gRz2zuGui4XHQPoaw/DkIMn7EyAzeSpRgbgs1LgMcEqqKsIEx+spCga3jsQ==
"@gitlab/ui@13.9.0":
version "13.9.0"