提交 0d09054d 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 f44809bf
......@@ -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
......
......@@ -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"
......
......@@ -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"]
......
......@@ -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:
......
......@@ -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__(
......
......@@ -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>
......
<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>
......
......@@ -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);
});
};
......
......@@ -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
......
# 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
......@@ -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?
......
-# 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 }
---
title: Use alerts instead of toasts in Image Repository details
merge_request: 29685
author:
type: changed
---
title: Fix layout in issue view, on large screen some buttons were misaligned
merge_request: 30947
author: Michele (macno) Azzolari
type: fixed
---
title: Revert CODEOWNERS validation of Web requests in diff check
merge_request: 31087
author:
type: fixed
......@@ -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.
......
......@@ -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
......
......@@ -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
......@@ -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
......@@ -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
......
......@@ -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 ""
......
......@@ -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
......@@ -17,9 +17,5 @@ FactoryBot.define do
container { project }
end
factory :group_wiki do
container { association(:group, :wiki_repo) }
end
end
end
......@@ -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
......
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);
});
});
},
);
});
});
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);
});
});
});
......@@ -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());
});
});
});
......@@ -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
......
......@@ -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
......@@ -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
......
......@@ -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
......
......@@ -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}" }
......
......@@ -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) }
......
# 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
......@@ -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
......
......@@ -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"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册