Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
953b58d061
commit
8db5a83725
17 changed files with 428 additions and 145 deletions
|
@ -1,10 +1,12 @@
|
||||||
<script>
|
<script>
|
||||||
import { GlTooltipDirective, GlIcon, GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
|
import { GlTooltipDirective, GlIcon, GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui';
|
||||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import { n__ } from '~/locale';
|
import { n__ } from '~/locale';
|
||||||
|
import Tracking from '~/tracking';
|
||||||
|
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||||
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
|
||||||
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
import ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||||
|
import { joinPaths } from '~/lib/utils/url_utility';
|
||||||
import {
|
import {
|
||||||
LIST_DELETE_BUTTON_DISABLED,
|
LIST_DELETE_BUTTON_DISABLED,
|
||||||
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
|
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
|
||||||
|
@ -14,6 +16,9 @@ import {
|
||||||
IMAGE_FAILED_DELETED_STATUS,
|
IMAGE_FAILED_DELETED_STATUS,
|
||||||
IMAGE_MIGRATING_STATE,
|
IMAGE_MIGRATING_STATE,
|
||||||
COPY_IMAGE_PATH_TITLE,
|
COPY_IMAGE_PATH_TITLE,
|
||||||
|
IMAGE_FULL_PATH_LABEL,
|
||||||
|
TRACKING_ACTION_CLICK_SHOW_FULL_PATH,
|
||||||
|
TRACKING_LABEL_REGISTRY_IMAGE_LIST,
|
||||||
} from '../../constants/index';
|
} from '../../constants/index';
|
||||||
import DeleteButton from '../delete_button.vue';
|
import DeleteButton from '../delete_button.vue';
|
||||||
import CleanupStatus from './cleanup_status.vue';
|
import CleanupStatus from './cleanup_status.vue';
|
||||||
|
@ -24,6 +29,7 @@ export default {
|
||||||
ClipboardButton,
|
ClipboardButton,
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
GlSprintf,
|
GlSprintf,
|
||||||
|
GlButton,
|
||||||
GlIcon,
|
GlIcon,
|
||||||
ListItem,
|
ListItem,
|
||||||
GlSkeletonLoader,
|
GlSkeletonLoader,
|
||||||
|
@ -32,6 +38,7 @@ export default {
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: GlTooltipDirective,
|
GlTooltip: GlTooltipDirective,
|
||||||
},
|
},
|
||||||
|
mixins: [Tracking.mixin(), glFeatureFlagsMixin()],
|
||||||
inject: ['config'],
|
inject: ['config'],
|
||||||
props: {
|
props: {
|
||||||
item: {
|
item: {
|
||||||
|
@ -53,6 +60,12 @@ export default {
|
||||||
REMOVE_REPOSITORY_LABEL,
|
REMOVE_REPOSITORY_LABEL,
|
||||||
ROW_SCHEDULED_FOR_DELETION,
|
ROW_SCHEDULED_FOR_DELETION,
|
||||||
COPY_IMAGE_PATH_TITLE,
|
COPY_IMAGE_PATH_TITLE,
|
||||||
|
IMAGE_FULL_PATH_LABEL,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
showFullPath: false,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
disabledDelete() {
|
disabledDelete() {
|
||||||
|
@ -78,6 +91,16 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
imageName() {
|
imageName() {
|
||||||
|
if (this.glFeatures.containerRegistryShowShortenedPath) {
|
||||||
|
if (this.showFullPath) {
|
||||||
|
return this.item.path;
|
||||||
|
}
|
||||||
|
const projectPath = this.item?.project?.path ?? '';
|
||||||
|
if (this.item.name) {
|
||||||
|
return joinPaths(projectPath, this.item.name);
|
||||||
|
}
|
||||||
|
return projectPath;
|
||||||
|
}
|
||||||
return this.item.path;
|
return this.item.path;
|
||||||
},
|
},
|
||||||
routerLinkEvent() {
|
routerLinkEvent() {
|
||||||
|
@ -89,6 +112,15 @@ export default {
|
||||||
: LIST_DELETE_BUTTON_DISABLED;
|
: LIST_DELETE_BUTTON_DISABLED;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
hideButton() {
|
||||||
|
this.showFullPath = true;
|
||||||
|
this.$refs.imageName.$el.focus();
|
||||||
|
this.track(TRACKING_ACTION_CLICK_SHOW_FULL_PATH, {
|
||||||
|
label: TRACKING_LABEL_REGISTRY_IMAGE_LIST,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -103,7 +135,20 @@ export default {
|
||||||
:disabled="deleting"
|
:disabled="deleting"
|
||||||
>
|
>
|
||||||
<template #left-primary>
|
<template #left-primary>
|
||||||
|
<gl-button
|
||||||
|
v-if="glFeatures.containerRegistryShowShortenedPath && !showFullPath"
|
||||||
|
v-gl-tooltip="{
|
||||||
|
placement: 'top',
|
||||||
|
title: $options.i18n.IMAGE_FULL_PATH_LABEL,
|
||||||
|
}"
|
||||||
|
icon="ellipsis_h"
|
||||||
|
size="small"
|
||||||
|
class="gl-mr-2"
|
||||||
|
:aria-label="$options.i18n.IMAGE_FULL_PATH_LABEL"
|
||||||
|
@click="hideButton"
|
||||||
|
/>
|
||||||
<router-link
|
<router-link
|
||||||
|
ref="imageName"
|
||||||
class="gl-text-body gl-font-weight-bold"
|
class="gl-text-body gl-font-weight-bold"
|
||||||
data-testid="details-link"
|
data-testid="details-link"
|
||||||
data-qa-selector="registry_image_content"
|
data-qa-selector="registry_image_content"
|
||||||
|
|
|
@ -43,6 +43,13 @@ export const EMPTY_RESULT_MESSAGE = s__(
|
||||||
|
|
||||||
export const COPY_IMAGE_PATH_TITLE = s__('ContainerRegistry|Copy image path');
|
export const COPY_IMAGE_PATH_TITLE = s__('ContainerRegistry|Copy image path');
|
||||||
|
|
||||||
|
export const IMAGE_FULL_PATH_LABEL = s__('ContainerRegistry|Show full path');
|
||||||
|
|
||||||
|
// Tracking
|
||||||
|
|
||||||
|
export const TRACKING_LABEL_REGISTRY_IMAGE_LIST = 'registry_image_list';
|
||||||
|
export const TRACKING_ACTION_CLICK_SHOW_FULL_PATH = 'click_show_full_path';
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
|
|
||||||
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
|
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
|
||||||
|
|
|
@ -32,9 +32,6 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
statusIcon(data) {
|
statusIcon(data) {
|
||||||
if (data.parsingInProgress) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (data.status === TESTS_FAILED_STATUS) {
|
if (data.status === TESTS_FAILED_STATUS) {
|
||||||
return EXTENSION_ICONS.warning;
|
return EXTENSION_ICONS.warning;
|
||||||
}
|
}
|
||||||
|
@ -56,18 +53,19 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchCollapsedData() {
|
fetchCollapsedData() {
|
||||||
return axios.get(this.testResultsPath).then((res) => {
|
return axios.get(this.testResultsPath).then((response) => {
|
||||||
const { data = {}, status } = res;
|
const { data = {}, status } = response;
|
||||||
|
const { suites = [], summary = {} } = data;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...res,
|
...response,
|
||||||
data: {
|
data: {
|
||||||
hasSuiteError: data.suites?.some((suite) => suite.status === ERROR_STATUS),
|
hasSuiteError: suites.some((suite) => suite.status === ERROR_STATUS),
|
||||||
parsingInProgress: status === 204,
|
parsingInProgress: status === 204,
|
||||||
...data,
|
...data,
|
||||||
summary: {
|
summary: {
|
||||||
recentlyFailed: countRecentlyFailedTests(data.suites),
|
recentlyFailed: countRecentlyFailedTests(suites),
|
||||||
...data.summary,
|
...summary,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import { i18n } from './constants';
|
import { i18n } from './constants';
|
||||||
|
|
||||||
const textBuilder = (results, boldNumbers = false) => {
|
const textBuilder = (results, boldNumbers = false) => {
|
||||||
|
@ -65,6 +66,11 @@ export const reportSubTextBuilder = ({ suite_errors, summary }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const countRecentlyFailedTests = (subject) => {
|
export const countRecentlyFailedTests = (subject) => {
|
||||||
|
// return 0 count if subject is [], null, or undefined
|
||||||
|
if (isEmpty(subject)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// handle either a single report or an array of reports
|
// handle either a single report or an array of reports
|
||||||
const reports = !subject.length ? [subject] : subject;
|
const reports = !subject.length ? [subject] : subject;
|
||||||
|
|
||||||
|
@ -73,10 +79,10 @@ export const countRecentlyFailedTests = (subject) => {
|
||||||
return (
|
return (
|
||||||
[report.new_failures, report.existing_failures, report.resolved_failures]
|
[report.new_failures, report.existing_failures, report.resolved_failures]
|
||||||
// only count tests which have failed more than once
|
// only count tests which have failed more than once
|
||||||
.map(
|
.map((failureArray) => {
|
||||||
(failureArray) =>
|
if (!failureArray) return 0;
|
||||||
failureArray.filter((failure) => failure.recent_failures?.count > 1).length,
|
return failureArray.filter((failure) => failure.recent_failures?.count > 1).length;
|
||||||
)
|
})
|
||||||
.reduce((total, count) => total + count, 0)
|
.reduce((total, count) => total + count, 0)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,10 @@ module Groups
|
||||||
before_action :verify_container_registry_enabled!
|
before_action :verify_container_registry_enabled!
|
||||||
before_action :authorize_read_container_image!
|
before_action :authorize_read_container_image!
|
||||||
|
|
||||||
|
before_action do
|
||||||
|
push_frontend_feature_flag(:container_registry_show_shortened_path, group)
|
||||||
|
end
|
||||||
|
|
||||||
feature_category :container_registry
|
feature_category :container_registry
|
||||||
urgency :low
|
urgency :low
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@ module Projects
|
||||||
|
|
||||||
before_action :authorize_update_container_image!, only: [:destroy]
|
before_action :authorize_update_container_image!, only: [:destroy]
|
||||||
|
|
||||||
|
before_action do
|
||||||
|
push_frontend_feature_flag(:container_registry_show_shortened_path, project)
|
||||||
|
end
|
||||||
|
|
||||||
def index
|
def index
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html { ensure_root_container_repository! }
|
format.html { ensure_root_container_repository! }
|
||||||
|
|
|
@ -32,6 +32,10 @@ query getProjectContainerRepositories(
|
||||||
createdAt
|
createdAt
|
||||||
expirationPolicyStartedAt
|
expirationPolicyStartedAt
|
||||||
expirationPolicyCleanupStatus
|
expirationPolicyCleanupStatus
|
||||||
|
project {
|
||||||
|
id
|
||||||
|
path
|
||||||
|
}
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
pageInfo {
|
pageInfo {
|
||||||
|
@ -67,6 +71,10 @@ query getProjectContainerRepositories(
|
||||||
createdAt
|
createdAt
|
||||||
expirationPolicyStartedAt
|
expirationPolicyStartedAt
|
||||||
expirationPolicyCleanupStatus
|
expirationPolicyCleanupStatus
|
||||||
|
project {
|
||||||
|
id
|
||||||
|
path
|
||||||
|
}
|
||||||
__typename
|
__typename
|
||||||
}
|
}
|
||||||
pageInfo {
|
pageInfo {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Performance optimization')
|
= _('Performance optimization')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'ip_limits_content' } }
|
%section.settings.as-ip-limits.no-animate#js-ip-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'ip_limits_content' } }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('User and IP rate limits')
|
= _('User and IP rate limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
%section.settings.as-packages-limits.no-animate#js-packages-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'packages_limits_content' } }
|
%section.settings.as-packages-limits.no-animate#js-packages-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'packages_limits_content' } }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Package registry rate limits')
|
= _('Package registry rate limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -39,7 +39,7 @@
|
||||||
|
|
||||||
%section.settings.as-files-limits.no-animate#js-files-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-files-limits.no-animate#js-files-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Files API Rate Limits')
|
= _('Files API Rate Limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
|
|
||||||
%section.settings.as-search-limits.no-animate#js-search-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-search-limits.no-animate#js-search-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Search rate limits')
|
= _('Search rate limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
%section.settings.as-deprecated-limits.no-animate#js-deprecated-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-deprecated-limits.no-animate#js-deprecated-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Deprecated API rate limits')
|
= _('Deprecated API rate limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
|
|
||||||
%section.settings.as-git-lfs-limits.no-animate#js-git-lfs-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'git_lfs_limits_content' } }
|
%section.settings.as-git-lfs-limits.no-animate#js-git-lfs-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'git_lfs_limits_content' } }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Git LFS Rate Limits')
|
= _('Git LFS Rate Limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -85,7 +85,7 @@
|
||||||
|
|
||||||
%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'outbound_requests_content' } }
|
%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'outbound_requests_content' } }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= s_('OutboundRequests|Outbound requests')
|
= s_('OutboundRequests|Outbound requests')
|
||||||
|
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
|
|
||||||
%section.settings.as-protected-paths.no-animate#js-protected-paths-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-protected-paths.no-animate#js-protected-paths-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Protected paths')
|
= _('Protected paths')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
|
|
||||||
%section.settings.as-issue-limits.no-animate#js-issue-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-issue-limits.no-animate#js-issue-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Issues Rate Limits')
|
= _('Issues Rate Limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
|
|
||||||
%section.settings.as-note-limits.no-animate#js-note-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-note-limits.no-animate#js-note-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Notes rate limit')
|
= _('Notes rate limit')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
|
|
||||||
%section.settings.as-users-api-limits.no-animate#js-users-api-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-users-api-limits.no-animate#js-users-api-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Users API rate limit')
|
= _('Users API rate limit')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -147,7 +147,7 @@
|
||||||
|
|
||||||
%section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-import-export-limits.no-animate#js-import-export-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Import and export rate limits')
|
= _('Import and export rate limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
@ -159,7 +159,7 @@
|
||||||
|
|
||||||
%section.settings.as-pipeline-limits.no-animate#js-pipeline-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
%section.settings.as-pipeline-limits.no-animate#js-pipeline-limits-settings{ class: ('expanded' if expanded_by_default?) }
|
||||||
.settings-header
|
.settings-header
|
||||||
%h4
|
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||||
= _('Pipeline creation rate limits')
|
= _('Pipeline creation rate limits')
|
||||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||||
= expanded_by_default? ? _('Collapse') : _('Expand')
|
= expanded_by_default? ? _('Collapse') : _('Expand')
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
name: container_registry_show_shortened_path
|
||||||
|
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91548
|
||||||
|
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366808
|
||||||
|
milestone: '15.2'
|
||||||
|
type: development
|
||||||
|
group: group::package
|
||||||
|
default_enabled: false
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
name: gitlab_sli_new_counters
|
|
||||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90977
|
|
||||||
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1760
|
|
||||||
milestone: '15.2'
|
|
||||||
type: development
|
|
||||||
group: group::scalability
|
|
||||||
default_enabled: false
|
|
|
@ -48,24 +48,14 @@ module Gitlab
|
||||||
# This module is effectively an abstract class
|
# This module is effectively an abstract class
|
||||||
@initialized_with_combinations = possible_label_combinations.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
@initialized_with_combinations = possible_label_combinations.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||||
possible_label_combinations.each do |label_combination|
|
possible_label_combinations.each do |label_combination|
|
||||||
legacy_total_counter.get(label_combination)
|
total_counter.get(label_combination)
|
||||||
legacy_numerator_counter.get(label_combination)
|
numerator_counter.get(label_combination)
|
||||||
|
|
||||||
if ::Feature.enabled?(:gitlab_sli_new_counters)
|
|
||||||
total_counter.get(label_combination)
|
|
||||||
numerator_counter.get(label_combination)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def increment(labels:, increment_numerator:)
|
def increment(labels:, increment_numerator:)
|
||||||
legacy_total_counter.increment(labels)
|
total_counter.increment(labels)
|
||||||
legacy_numerator_counter.increment(labels) if increment_numerator
|
numerator_counter.increment(labels) if increment_numerator
|
||||||
|
|
||||||
if ::Feature.enabled?(:gitlab_sli_new_counters)
|
|
||||||
total_counter.increment(labels)
|
|
||||||
numerator_counter.increment(labels) if increment_numerator
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def initialized?
|
def initialized?
|
||||||
|
@ -75,11 +65,7 @@ module Gitlab
|
||||||
private
|
private
|
||||||
|
|
||||||
def total_counter
|
def total_counter
|
||||||
prometheus.counter(counter_name('total', '_'), "Total number of measurements for #{name}")
|
prometheus.counter(counter_name('total'), "Total number of measurements for #{name}")
|
||||||
end
|
|
||||||
|
|
||||||
def legacy_total_counter
|
|
||||||
prometheus.counter(counter_name('total', ':'), "Total number of measurements for #{name}")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def prometheus
|
def prometheus
|
||||||
|
@ -95,16 +81,12 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def counter_name(suffix, separator)
|
def counter_name(suffix)
|
||||||
[COUNTER_PREFIX, "#{name}_apdex", suffix].join(separator).to_sym
|
[COUNTER_PREFIX, "#{name}_apdex", suffix].join('_').to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
def numerator_counter
|
def numerator_counter
|
||||||
prometheus.counter(counter_name('success_total', '_'), "Number of successful measurements for #{name}")
|
prometheus.counter(counter_name('success_total'), "Number of successful measurements for #{name}")
|
||||||
end
|
|
||||||
|
|
||||||
def legacy_numerator_counter
|
|
||||||
prometheus.counter(counter_name('success_total', ':'), "Legacy number of successful measurements for #{name}")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -117,16 +99,12 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def counter_name(suffix, separator)
|
def counter_name(suffix)
|
||||||
[COUNTER_PREFIX, name, suffix].join(separator).to_sym
|
[COUNTER_PREFIX, name, suffix].join('_').to_sym
|
||||||
end
|
end
|
||||||
|
|
||||||
def numerator_counter
|
def numerator_counter
|
||||||
prometheus.counter(counter_name('error_total', '_'), "Number of error measurements for #{name}")
|
prometheus.counter(counter_name('error_total'), "Number of error measurements for #{name}")
|
||||||
end
|
|
||||||
|
|
||||||
def legacy_numerator_counter
|
|
||||||
prometheus.counter(counter_name('error_total', ':'), "Number of error measurements for #{name}")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10086,6 +10086,9 @@ msgstr ""
|
||||||
msgid "ContainerRegistry|Set up cleanup"
|
msgid "ContainerRegistry|Set up cleanup"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ContainerRegistry|Show full path"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ContainerRegistry|Some tags were not deleted"
|
msgid "ContainerRegistry|Some tags were not deleted"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { GlIcon, GlSprintf, GlSkeletonLoader } from '@gitlab/ui';
|
import { GlIcon, GlSprintf, GlSkeletonLoader, GlButton } from '@gitlab/ui';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||||
|
import { mockTracking } from 'helpers/tracking_helper';
|
||||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||||
import DeleteButton from '~/packages_and_registries/container_registry/explorer/components/delete_button.vue';
|
import DeleteButton from '~/packages_and_registries/container_registry/explorer/components/delete_button.vue';
|
||||||
import CleanupStatus from '~/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue';
|
import CleanupStatus from '~/packages_and_registries/container_registry/explorer/components/list_page/cleanup_status.vue';
|
||||||
|
@ -30,13 +31,15 @@ describe('Image List Row', () => {
|
||||||
const findCleanupStatus = () => wrapper.findComponent(CleanupStatus);
|
const findCleanupStatus = () => wrapper.findComponent(CleanupStatus);
|
||||||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||||
const findListItemComponent = () => wrapper.findComponent(ListItem);
|
const findListItemComponent = () => wrapper.findComponent(ListItem);
|
||||||
|
const findShowFullPathButton = () => wrapper.findComponent(GlButton);
|
||||||
|
|
||||||
const mountComponent = (props) => {
|
const mountComponent = (props, features = {}) => {
|
||||||
wrapper = shallowMount(Component, {
|
wrapper = shallowMount(Component, {
|
||||||
stubs: {
|
stubs: {
|
||||||
RouterLink,
|
RouterLink,
|
||||||
GlSprintf,
|
GlSprintf,
|
||||||
ListItem,
|
ListItem,
|
||||||
|
GlButton,
|
||||||
},
|
},
|
||||||
propsData: {
|
propsData: {
|
||||||
item,
|
item,
|
||||||
|
@ -44,6 +47,9 @@ describe('Image List Row', () => {
|
||||||
},
|
},
|
||||||
provide: {
|
provide: {
|
||||||
config: {},
|
config: {},
|
||||||
|
glFeatures: {
|
||||||
|
...features,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
GlTooltip: createMockDirective(),
|
GlTooltip: createMockDirective(),
|
||||||
|
@ -95,7 +101,7 @@ describe('Image List Row', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`when the image has no name lists the path`, () => {
|
it('when the image has no name lists the path', () => {
|
||||||
mountComponent({ item: { ...item, name: '' } });
|
mountComponent({ item: { ...item, name: '' } });
|
||||||
|
|
||||||
expect(findDetailsLink().text()).toBe(item.path);
|
expect(findDetailsLink().text()).toBe(item.path);
|
||||||
|
@ -143,6 +149,35 @@ describe('Image List Row', () => {
|
||||||
expect(findClipboardButton().attributes('disabled')).toBe('true');
|
expect(findClipboardButton().attributes('disabled')).toBe('true');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when containerRegistryShowShortenedPath feature enabled', () => {
|
||||||
|
let trackingSpy;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mountComponent({}, { containerRegistryShowShortenedPath: true });
|
||||||
|
trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders shortened name of image', () => {
|
||||||
|
expect(findShowFullPathButton().exists()).toBe(true);
|
||||||
|
expect(findDetailsLink().text()).toBe('gitlab-test/rails-12009');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('clicking on shortened name of image hides the button & shows full path', async () => {
|
||||||
|
const btn = findShowFullPathButton();
|
||||||
|
const mockFocusFn = jest.fn();
|
||||||
|
wrapper.vm.$refs.imageName.$el.focus = mockFocusFn;
|
||||||
|
|
||||||
|
await btn.trigger('click');
|
||||||
|
|
||||||
|
expect(findShowFullPathButton().exists()).toBe(false);
|
||||||
|
expect(findDetailsLink().text()).toBe(item.path);
|
||||||
|
expect(mockFocusFn).toHaveBeenCalled();
|
||||||
|
expect(trackingSpy).toHaveBeenCalledWith(undefined, 'click_show_full_path', {
|
||||||
|
label: 'registry_image_list',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('delete button', () => {
|
describe('delete button', () => {
|
||||||
|
|
|
@ -11,6 +11,10 @@ export const imagesListResponse = [
|
||||||
createdAt: '2020-11-03T13:29:21Z',
|
createdAt: '2020-11-03T13:29:21Z',
|
||||||
expirationPolicyStartedAt: null,
|
expirationPolicyStartedAt: null,
|
||||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||||
|
project: {
|
||||||
|
id: 'gid://gitlab/Project/22',
|
||||||
|
path: 'gitlab-test',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
__typename: 'ContainerRepository',
|
__typename: 'ContainerRepository',
|
||||||
|
@ -24,6 +28,10 @@ export const imagesListResponse = [
|
||||||
createdAt: '2020-09-21T06:57:43Z',
|
createdAt: '2020-09-21T06:57:43Z',
|
||||||
expirationPolicyStartedAt: null,
|
expirationPolicyStartedAt: null,
|
||||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||||
|
project: {
|
||||||
|
id: 'gid://gitlab/Project/22',
|
||||||
|
path: 'gitlab-test',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -72,14 +72,23 @@ describe('Test report extension', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('summary', () => {
|
describe('summary', () => {
|
||||||
it('displays loading text', () => {
|
it('displays loading state initially', () => {
|
||||||
mockApi(httpStatusCodes.OK);
|
mockApi(httpStatusCodes.OK);
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
expect(wrapper.text()).toContain(i18n.loading);
|
expect(wrapper.text()).toContain(i18n.loading);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('displays failed loading text', async () => {
|
it('with a 204 response, continues to display loading state', async () => {
|
||||||
|
mockApi(httpStatusCodes.NO_CONTENT, '');
|
||||||
|
createComponent();
|
||||||
|
|
||||||
|
await waitForPromises();
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain(i18n.loading);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('with an error response, displays failed to load text', async () => {
|
||||||
mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR);
|
mockApi(httpStatusCodes.INTERNAL_SERVER_ERROR);
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
|
|
242
spec/frontend/vue_mr_widget/extensions/test_report/utils_spec.js
Normal file
242
spec/frontend/vue_mr_widget/extensions/test_report/utils_spec.js
Normal file
|
@ -0,0 +1,242 @@
|
||||||
|
import * as utils from '~/vue_merge_request_widget/extensions/test_report/utils';
|
||||||
|
|
||||||
|
describe('test report widget extension utils', () => {
|
||||||
|
describe('summaryTextbuilder', () => {
|
||||||
|
it('should render text for no changed results in multiple tests', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}no%{strong_end} changed test results, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for no changed results in one test', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { total: 1 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}no%{strong_end} changed test results, %{strong_start}1%{strong_end} total test',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple failed results', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { failed: 3, total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}3%{strong_end} failed, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple errored results', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { errored: 7, total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}7%{strong_end} errors, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple fixed results', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { resolved: 4, total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}4%{strong_end} fixed test results, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple fixed, and multiple failed results', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { failed: 3, resolved: 4, total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}3%{strong_end} failed and %{strong_start}4%{strong_end} fixed test results, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for a singular fixed, and a singular failed result', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { failed: 1, resolved: 1, total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}1%{strong_end} failed and %{strong_start}1%{strong_end} fixed test result, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for singular failed, errored, and fixed results', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { failed: 1, errored: 1, resolved: 1, total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}1%{strong_end} failed, %{strong_start}1%{strong_end} error and %{strong_start}1%{strong_end} fixed test result, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple failed, errored, and fixed results', () => {
|
||||||
|
const name = 'Test summary';
|
||||||
|
const data = { failed: 2, errored: 3, resolved: 4, total: 10 };
|
||||||
|
const result = utils.summaryTextBuilder(name, data);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
'Test summary: %{strong_start}2%{strong_end} failed, %{strong_start}3%{strong_end} errors and %{strong_start}4%{strong_end} fixed test results, %{strong_start}10%{strong_end} total tests',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('reportTextBuilder', () => {
|
||||||
|
const name = 'Rspec';
|
||||||
|
|
||||||
|
it('should render text for no changed results in multiple tests', () => {
|
||||||
|
const data = { name, summary: { total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: no changed test results, 10 total tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for no changed results in one test', () => {
|
||||||
|
const data = { name, summary: { total: 1 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: no changed test results, 1 total test');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple failed results', () => {
|
||||||
|
const data = { name, summary: { failed: 3, total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: 3 failed, 10 total tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple errored results', () => {
|
||||||
|
const data = { name, summary: { errored: 7, total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: 7 errors, 10 total tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple fixed results', () => {
|
||||||
|
const data = { name, summary: { resolved: 4, total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: 4 fixed test results, 10 total tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple fixed, and multiple failed results', () => {
|
||||||
|
const data = { name, summary: { failed: 3, resolved: 4, total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: 3 failed and 4 fixed test results, 10 total tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for a singular fixed, and a singular failed result', () => {
|
||||||
|
const data = { name, summary: { failed: 1, resolved: 1, total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: 1 failed and 1 fixed test result, 10 total tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for singular failed, errored, and fixed results', () => {
|
||||||
|
const data = { name, summary: { failed: 1, errored: 1, resolved: 1, total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: 1 failed, 1 error and 1 fixed test result, 10 total tests');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render text for multiple failed, errored, and fixed results', () => {
|
||||||
|
const data = { name, summary: { failed: 2, errored: 3, resolved: 4, total: 10 } };
|
||||||
|
const result = utils.reportTextBuilder(data);
|
||||||
|
|
||||||
|
expect(result).toBe('Rspec: 2 failed, 3 errors and 4 fixed test results, 10 total tests');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('recentFailuresTextBuilder', () => {
|
||||||
|
it.each`
|
||||||
|
recentlyFailed | failed | expected
|
||||||
|
${0} | ${1} | ${''}
|
||||||
|
${1} | ${1} | ${'1 out of 1 failed test has failed more than once in the last 14 days'}
|
||||||
|
${1} | ${2} | ${'1 out of 2 failed tests has failed more than once in the last 14 days'}
|
||||||
|
${2} | ${3} | ${'2 out of 3 failed tests have failed more than once in the last 14 days'}
|
||||||
|
`(
|
||||||
|
'should render summary for $recentlyFailed out of $failed failures',
|
||||||
|
({ recentlyFailed, failed, expected }) => {
|
||||||
|
const result = utils.recentFailuresTextBuilder({ recentlyFailed, failed });
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('countRecentlyFailedTests', () => {
|
||||||
|
it('counts tests with more than one recent failure in a report', () => {
|
||||||
|
const report = {
|
||||||
|
new_failures: [{ recent_failures: { count: 2 } }],
|
||||||
|
existing_failures: [{ recent_failures: { count: 1 } }],
|
||||||
|
resolved_failures: [{ recent_failures: { count: 20 } }, { recent_failures: { count: 5 } }],
|
||||||
|
};
|
||||||
|
const result = utils.countRecentlyFailedTests(report);
|
||||||
|
|
||||||
|
expect(result).toBe(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('counts tests with more than one recent failure in an array of reports', () => {
|
||||||
|
const reports = [
|
||||||
|
{
|
||||||
|
new_failures: [{ recent_failures: { count: 2 } }],
|
||||||
|
existing_failures: [
|
||||||
|
{ recent_failures: { count: 20 } },
|
||||||
|
{ recent_failures: { count: 5 } },
|
||||||
|
],
|
||||||
|
resolved_failures: [{ recent_failures: { count: 2 } }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new_failures: [{ recent_failures: { count: 8 } }, { recent_failures: { count: 14 } }],
|
||||||
|
existing_failures: [{ recent_failures: { count: 1 } }],
|
||||||
|
resolved_failures: [{ recent_failures: { count: 7 } }, { recent_failures: { count: 5 } }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = utils.countRecentlyFailedTests(reports);
|
||||||
|
|
||||||
|
expect(result).toBe(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
[],
|
||||||
|
{},
|
||||||
|
null,
|
||||||
|
undefined,
|
||||||
|
{ new_failures: undefined },
|
||||||
|
[{ existing_failures: null }],
|
||||||
|
{ resolved_failures: [{}] },
|
||||||
|
[{ new_failures: [{ recent_failures: {} }] }],
|
||||||
|
])('returns 0 when subject is %s', (subject) => {
|
||||||
|
const result = utils.countRecentlyFailedTests(subject);
|
||||||
|
|
||||||
|
expect(result).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('formatFilePath', () => {
|
||||||
|
it.each`
|
||||||
|
file | expected
|
||||||
|
${'./test.js'} | ${'test.js'}
|
||||||
|
${'/test.js'} | ${'test.js'}
|
||||||
|
${'.//////////////test.js'} | ${'test.js'}
|
||||||
|
${'test.js'} | ${'test.js'}
|
||||||
|
${'mock/path./test.js'} | ${'mock/path./test.js'}
|
||||||
|
${'./mock/path./test.js'} | ${'mock/path./test.js'}
|
||||||
|
`('should format $file to be $expected', ({ file, expected }) => {
|
||||||
|
expect(utils.formatFilePath(file)).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
# Switch this back to `fast_spec_helper` when removing the `gitlab_sli_new_counters`
|
require 'fast_spec_helper'
|
||||||
# feature flag
|
|
||||||
require 'spec_helper'
|
|
||||||
|
|
||||||
RSpec.describe Gitlab::Metrics::Sli do
|
RSpec.describe Gitlab::Metrics::Sli do
|
||||||
let(:prometheus) { double("prometheus") }
|
let(:prometheus) { double("prometheus") }
|
||||||
|
@ -20,16 +18,12 @@ RSpec.describe Gitlab::Metrics::Sli do
|
||||||
it 'allows different SLIs to be defined on each subclass' do
|
it 'allows different SLIs to be defined on each subclass' do
|
||||||
apdex_counters = [
|
apdex_counters = [
|
||||||
fake_total_counter('foo_apdex'),
|
fake_total_counter('foo_apdex'),
|
||||||
fake_numerator_counter('foo_apdex', 'success'),
|
fake_numerator_counter('foo_apdex', 'success')
|
||||||
fake_total_counter('foo_apdex', ':'),
|
|
||||||
fake_numerator_counter('foo_apdex', 'success', ':')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
error_rate_counters = [
|
error_rate_counters = [
|
||||||
fake_total_counter('foo'),
|
fake_total_counter('foo'),
|
||||||
fake_numerator_counter('foo', 'error'),
|
fake_numerator_counter('foo', 'error')
|
||||||
fake_total_counter('foo', ':'),
|
|
||||||
fake_numerator_counter('foo', 'error', ':')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
apdex = described_class::Apdex.initialize_sli(:foo, [{ hello: :world }])
|
apdex = described_class::Apdex.initialize_sli(:foo, [{ hello: :world }])
|
||||||
|
@ -84,9 +78,7 @@ RSpec.describe Gitlab::Metrics::Sli do
|
||||||
it 'returns and stores a new initialized SLI' do
|
it 'returns and stores a new initialized SLI' do
|
||||||
counters = [
|
counters = [
|
||||||
fake_total_counter("bar#{subclass_info[:suffix]}"),
|
fake_total_counter("bar#{subclass_info[:suffix]}"),
|
||||||
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator]),
|
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
|
||||||
fake_total_counter("bar#{subclass_info[:suffix]}", ':'),
|
|
||||||
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
sli = described_class.initialize_sli(:bar, [{ hello: :world }])
|
sli = described_class.initialize_sli(:bar, [{ hello: :world }])
|
||||||
|
@ -99,9 +91,7 @@ RSpec.describe Gitlab::Metrics::Sli do
|
||||||
it 'does not change labels for an already-initialized SLI' do
|
it 'does not change labels for an already-initialized SLI' do
|
||||||
counters = [
|
counters = [
|
||||||
fake_total_counter("bar#{subclass_info[:suffix]}"),
|
fake_total_counter("bar#{subclass_info[:suffix]}"),
|
||||||
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator]),
|
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
|
||||||
fake_total_counter("bar#{subclass_info[:suffix]}", ':'),
|
|
||||||
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
sli = described_class.initialize_sli(:bar, [{ hello: :world }])
|
sli = described_class.initialize_sli(:bar, [{ hello: :world }])
|
||||||
|
@ -122,8 +112,6 @@ RSpec.describe Gitlab::Metrics::Sli do
|
||||||
before do
|
before do
|
||||||
fake_total_counter("boom#{subclass_info[:suffix]}")
|
fake_total_counter("boom#{subclass_info[:suffix]}")
|
||||||
fake_numerator_counter("boom#{subclass_info[:suffix]}", subclass_info[:numerator])
|
fake_numerator_counter("boom#{subclass_info[:suffix]}", subclass_info[:numerator])
|
||||||
fake_total_counter("boom#{subclass_info[:suffix]}", ':')
|
|
||||||
fake_numerator_counter("boom#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is true when an SLI was initialized with labels' do
|
it 'is true when an SLI was initialized with labels' do
|
||||||
|
@ -142,9 +130,7 @@ RSpec.describe Gitlab::Metrics::Sli do
|
||||||
it 'initializes counters for the passed label combinations' do
|
it 'initializes counters for the passed label combinations' do
|
||||||
counters = [
|
counters = [
|
||||||
fake_total_counter("hey#{subclass_info[:suffix]}"),
|
fake_total_counter("hey#{subclass_info[:suffix]}"),
|
||||||
fake_numerator_counter("hey#{subclass_info[:suffix]}", subclass_info[:numerator]),
|
fake_numerator_counter("hey#{subclass_info[:suffix]}", subclass_info[:numerator])
|
||||||
fake_total_counter("hey#{subclass_info[:suffix]}", ':'),
|
|
||||||
fake_numerator_counter("hey#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
|
|
||||||
]
|
]
|
||||||
|
|
||||||
described_class.new(:hey).initialize_counters([{ foo: 'bar' }, { foo: 'baz' }])
|
described_class.new(:hey).initialize_counters([{ foo: 'bar' }, { foo: 'baz' }])
|
||||||
|
@ -152,43 +138,18 @@ RSpec.describe Gitlab::Metrics::Sli do
|
||||||
expect(counters).to all(have_received(:get).with({ foo: 'bar' }))
|
expect(counters).to all(have_received(:get).with({ foo: 'bar' }))
|
||||||
expect(counters).to all(have_received(:get).with({ foo: 'baz' }))
|
expect(counters).to all(have_received(:get).with({ foo: 'baz' }))
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when `gitlab_sli_new_counters` is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(gitlab_sli_new_counters: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not initialize the new counters', :aggregate_failures do
|
|
||||||
new_total_counter = fake_total_counter("bar#{subclass_info[:suffix]}")
|
|
||||||
new_numerator_counter = fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
|
|
||||||
|
|
||||||
fake_total_counter("bar#{subclass_info[:suffix]}", ':')
|
|
||||||
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
|
|
||||||
|
|
||||||
described_class.new(:bar).initialize_counters([{ hello: :world }])
|
|
||||||
|
|
||||||
expect(new_total_counter).not_to have_received(:get)
|
|
||||||
expect(new_numerator_counter).not_to have_received(:get)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#increment" do
|
describe "#increment" do
|
||||||
let!(:sli) { described_class.new(:heyo) }
|
let!(:sli) { described_class.new(:heyo) }
|
||||||
let!(:total_counter) { fake_total_counter("heyo#{subclass_info[:suffix]}") }
|
let!(:total_counter) { fake_total_counter("heyo#{subclass_info[:suffix]}") }
|
||||||
let!(:numerator_counter) { fake_numerator_counter("heyo#{subclass_info[:suffix]}", subclass_info[:numerator]) }
|
let!(:numerator_counter) { fake_numerator_counter("heyo#{subclass_info[:suffix]}", subclass_info[:numerator]) }
|
||||||
let!(:legacy_total_counter) { fake_total_counter("heyo#{subclass_info[:suffix]}", ':') }
|
|
||||||
let!(:legacy_numerator_counter) do
|
|
||||||
fake_numerator_counter("heyo#{subclass_info[:suffix]}", subclass_info[:numerator], ':')
|
|
||||||
end
|
|
||||||
|
|
||||||
it "increments both counters for labels when #{subclass_info[:numerator]} is true" do
|
it "increments both counters for labels when #{subclass_info[:numerator]} is true" do
|
||||||
sli.increment(labels: { hello: "world" }, subclass_info[:numerator] => true)
|
sli.increment(labels: { hello: "world" }, subclass_info[:numerator] => true)
|
||||||
|
|
||||||
expect(total_counter).to have_received(:increment).with({ hello: 'world' })
|
expect(total_counter).to have_received(:increment).with({ hello: 'world' })
|
||||||
expect(numerator_counter).to have_received(:increment).with({ hello: 'world' })
|
expect(numerator_counter).to have_received(:increment).with({ hello: 'world' })
|
||||||
expect(legacy_total_counter).to have_received(:increment).with({ hello: 'world' })
|
|
||||||
expect(legacy_numerator_counter).to have_received(:increment).with({ hello: 'world' })
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "only increments the total counters for labels when #{subclass_info[:numerator]} is false" do
|
it "only increments the total counters for labels when #{subclass_info[:numerator]} is false" do
|
||||||
|
@ -196,31 +157,6 @@ RSpec.describe Gitlab::Metrics::Sli do
|
||||||
|
|
||||||
expect(total_counter).to have_received(:increment).with({ hello: 'world' })
|
expect(total_counter).to have_received(:increment).with({ hello: 'world' })
|
||||||
expect(numerator_counter).not_to have_received(:increment).with({ hello: 'world' })
|
expect(numerator_counter).not_to have_received(:increment).with({ hello: 'world' })
|
||||||
expect(legacy_total_counter).to have_received(:increment).with({ hello: 'world' })
|
|
||||||
expect(legacy_numerator_counter).not_to have_received(:increment).with({ hello: 'world' })
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when `gitlab_sli_new_counters` is disabled' do
|
|
||||||
before do
|
|
||||||
stub_feature_flags(gitlab_sli_new_counters: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does increment new counters', :aggregate_failures do
|
|
||||||
new_total_counter = fake_total_counter("bar#{subclass_info[:suffix]}")
|
|
||||||
new_numerator_counter = fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
|
|
||||||
|
|
||||||
legacy_total_counter = fake_total_counter("bar#{subclass_info[:suffix]}", ':')
|
|
||||||
legacy_numerator_counter = fake_numerator_counter(
|
|
||||||
"bar#{subclass_info[:suffix]}", subclass_info[:numerator], ':'
|
|
||||||
)
|
|
||||||
|
|
||||||
described_class.new(:bar).increment(labels: { hello: 'world' }, subclass_info[:numerator] => true)
|
|
||||||
|
|
||||||
expect(new_total_counter).not_to have_received(:increment)
|
|
||||||
expect(new_numerator_counter).not_to have_received(:increment)
|
|
||||||
expect(legacy_total_counter).to have_received(:increment)
|
|
||||||
expect(legacy_numerator_counter).to have_received(:increment)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue