Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
953b58d061
commit
8db5a83725
|
@ -1,10 +1,12 @@
|
|||
<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 { 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 ListItem from '~/vue_shared/components/registry/list_item.vue';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import {
|
||||
LIST_DELETE_BUTTON_DISABLED,
|
||||
LIST_DELETE_BUTTON_DISABLED_FOR_MIGRATION,
|
||||
|
@ -14,6 +16,9 @@ import {
|
|||
IMAGE_FAILED_DELETED_STATUS,
|
||||
IMAGE_MIGRATING_STATE,
|
||||
COPY_IMAGE_PATH_TITLE,
|
||||
IMAGE_FULL_PATH_LABEL,
|
||||
TRACKING_ACTION_CLICK_SHOW_FULL_PATH,
|
||||
TRACKING_LABEL_REGISTRY_IMAGE_LIST,
|
||||
} from '../../constants/index';
|
||||
import DeleteButton from '../delete_button.vue';
|
||||
import CleanupStatus from './cleanup_status.vue';
|
||||
|
@ -24,6 +29,7 @@ export default {
|
|||
ClipboardButton,
|
||||
DeleteButton,
|
||||
GlSprintf,
|
||||
GlButton,
|
||||
GlIcon,
|
||||
ListItem,
|
||||
GlSkeletonLoader,
|
||||
|
@ -32,6 +38,7 @@ export default {
|
|||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
mixins: [Tracking.mixin(), glFeatureFlagsMixin()],
|
||||
inject: ['config'],
|
||||
props: {
|
||||
item: {
|
||||
|
@ -53,6 +60,12 @@ export default {
|
|||
REMOVE_REPOSITORY_LABEL,
|
||||
ROW_SCHEDULED_FOR_DELETION,
|
||||
COPY_IMAGE_PATH_TITLE,
|
||||
IMAGE_FULL_PATH_LABEL,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showFullPath: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
disabledDelete() {
|
||||
|
@ -78,6 +91,16 @@ export default {
|
|||
);
|
||||
},
|
||||
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;
|
||||
},
|
||||
routerLinkEvent() {
|
||||
|
@ -89,6 +112,15 @@ export default {
|
|||
: 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>
|
||||
|
||||
|
@ -103,7 +135,20 @@ export default {
|
|||
:disabled="deleting"
|
||||
>
|
||||
<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
|
||||
ref="imageName"
|
||||
class="gl-text-body gl-font-weight-bold"
|
||||
data-testid="details-link"
|
||||
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 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
|
||||
|
||||
export const IMAGE_DELETE_SCHEDULED_STATUS = 'DELETE_SCHEDULED';
|
||||
|
|
|
@ -32,9 +32,6 @@ export default {
|
|||
};
|
||||
},
|
||||
statusIcon(data) {
|
||||
if (data.parsingInProgress) {
|
||||
return null;
|
||||
}
|
||||
if (data.status === TESTS_FAILED_STATUS) {
|
||||
return EXTENSION_ICONS.warning;
|
||||
}
|
||||
|
@ -56,18 +53,19 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
fetchCollapsedData() {
|
||||
return axios.get(this.testResultsPath).then((res) => {
|
||||
const { data = {}, status } = res;
|
||||
return axios.get(this.testResultsPath).then((response) => {
|
||||
const { data = {}, status } = response;
|
||||
const { suites = [], summary = {} } = data;
|
||||
|
||||
return {
|
||||
...res,
|
||||
...response,
|
||||
data: {
|
||||
hasSuiteError: data.suites?.some((suite) => suite.status === ERROR_STATUS),
|
||||
hasSuiteError: suites.some((suite) => suite.status === ERROR_STATUS),
|
||||
parsingInProgress: status === 204,
|
||||
...data,
|
||||
summary: {
|
||||
recentlyFailed: countRecentlyFailedTests(data.suites),
|
||||
...data.summary,
|
||||
recentlyFailed: countRecentlyFailedTests(suites),
|
||||
...summary,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { isEmpty } from 'lodash';
|
||||
import { i18n } from './constants';
|
||||
|
||||
const textBuilder = (results, boldNumbers = false) => {
|
||||
|
@ -65,6 +66,11 @@ export const reportSubTextBuilder = ({ suite_errors, summary }) => {
|
|||
};
|
||||
|
||||
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
|
||||
const reports = !subject.length ? [subject] : subject;
|
||||
|
||||
|
@ -73,10 +79,10 @@ export const countRecentlyFailedTests = (subject) => {
|
|||
return (
|
||||
[report.new_failures, report.existing_failures, report.resolved_failures]
|
||||
// only count tests which have failed more than once
|
||||
.map(
|
||||
(failureArray) =>
|
||||
failureArray.filter((failure) => failure.recent_failures?.count > 1).length,
|
||||
)
|
||||
.map((failureArray) => {
|
||||
if (!failureArray) return 0;
|
||||
return failureArray.filter((failure) => failure.recent_failures?.count > 1).length;
|
||||
})
|
||||
.reduce((total, count) => total + count, 0)
|
||||
);
|
||||
})
|
||||
|
|
|
@ -8,6 +8,10 @@ module Groups
|
|||
before_action :verify_container_registry_enabled!
|
||||
before_action :authorize_read_container_image!
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:container_registry_show_shortened_path, group)
|
||||
end
|
||||
|
||||
feature_category :container_registry
|
||||
urgency :low
|
||||
|
||||
|
|
|
@ -8,6 +8,10 @@ module Projects
|
|||
|
||||
before_action :authorize_update_container_image!, only: [:destroy]
|
||||
|
||||
before_action do
|
||||
push_frontend_feature_flag(:container_registry_show_shortened_path, project)
|
||||
end
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
format.html { ensure_root_container_repository! }
|
||||
|
|
|
@ -32,6 +32,10 @@ query getProjectContainerRepositories(
|
|||
createdAt
|
||||
expirationPolicyStartedAt
|
||||
expirationPolicyCleanupStatus
|
||||
project {
|
||||
id
|
||||
path
|
||||
}
|
||||
__typename
|
||||
}
|
||||
pageInfo {
|
||||
|
@ -67,6 +71,10 @@ query getProjectContainerRepositories(
|
|||
createdAt
|
||||
expirationPolicyStartedAt
|
||||
expirationPolicyCleanupStatus
|
||||
project {
|
||||
id
|
||||
path
|
||||
}
|
||||
__typename
|
||||
}
|
||||
pageInfo {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
%section.settings.as-performance.no-animate#js-performance-settings{ class: ('expanded' if expanded_by_default?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Performance optimization')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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' } }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('User and IP rate limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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' } }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Package registry rate limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Files API Rate Limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Search rate limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Deprecated API rate limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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' } }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Git LFS Rate Limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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' } }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= s_('OutboundRequests|Outbound requests')
|
||||
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Protected paths')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Issues Rate Limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Notes rate limit')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Users API rate limit')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Import and export rate limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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?) }
|
||||
.settings-header
|
||||
%h4
|
||||
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only
|
||||
= _('Pipeline creation rate limits')
|
||||
= render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do
|
||||
= 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
|
||||
@initialized_with_combinations = possible_label_combinations.any? # rubocop:disable Gitlab/ModuleWithInstanceVariables
|
||||
possible_label_combinations.each do |label_combination|
|
||||
legacy_total_counter.get(label_combination)
|
||||
legacy_numerator_counter.get(label_combination)
|
||||
|
||||
if ::Feature.enabled?(:gitlab_sli_new_counters)
|
||||
total_counter.get(label_combination)
|
||||
numerator_counter.get(label_combination)
|
||||
end
|
||||
total_counter.get(label_combination)
|
||||
numerator_counter.get(label_combination)
|
||||
end
|
||||
end
|
||||
|
||||
def increment(labels:, increment_numerator:)
|
||||
legacy_total_counter.increment(labels)
|
||||
legacy_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
|
||||
total_counter.increment(labels)
|
||||
numerator_counter.increment(labels) if increment_numerator
|
||||
end
|
||||
|
||||
def initialized?
|
||||
|
@ -75,11 +65,7 @@ module Gitlab
|
|||
private
|
||||
|
||||
def total_counter
|
||||
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}")
|
||||
prometheus.counter(counter_name('total'), "Total number of measurements for #{name}")
|
||||
end
|
||||
|
||||
def prometheus
|
||||
|
@ -95,16 +81,12 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def counter_name(suffix, separator)
|
||||
[COUNTER_PREFIX, "#{name}_apdex", suffix].join(separator).to_sym
|
||||
def counter_name(suffix)
|
||||
[COUNTER_PREFIX, "#{name}_apdex", suffix].join('_').to_sym
|
||||
end
|
||||
|
||||
def numerator_counter
|
||||
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}")
|
||||
prometheus.counter(counter_name('success_total'), "Number of successful measurements for #{name}")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -117,16 +99,12 @@ module Gitlab
|
|||
|
||||
private
|
||||
|
||||
def counter_name(suffix, separator)
|
||||
[COUNTER_PREFIX, name, suffix].join(separator).to_sym
|
||||
def counter_name(suffix)
|
||||
[COUNTER_PREFIX, name, suffix].join('_').to_sym
|
||||
end
|
||||
|
||||
def numerator_counter
|
||||
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}")
|
||||
prometheus.counter(counter_name('error_total'), "Number of error measurements for #{name}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10086,6 +10086,9 @@ msgstr ""
|
|||
msgid "ContainerRegistry|Set up cleanup"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Show full path"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContainerRegistry|Some tags were not deleted"
|
||||
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 { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
|
||||
import { mockTracking } from 'helpers/tracking_helper';
|
||||
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
|
||||
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';
|
||||
|
@ -30,13 +31,15 @@ describe('Image List Row', () => {
|
|||
const findCleanupStatus = () => wrapper.findComponent(CleanupStatus);
|
||||
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
|
||||
const findListItemComponent = () => wrapper.findComponent(ListItem);
|
||||
const findShowFullPathButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
const mountComponent = (props) => {
|
||||
const mountComponent = (props, features = {}) => {
|
||||
wrapper = shallowMount(Component, {
|
||||
stubs: {
|
||||
RouterLink,
|
||||
GlSprintf,
|
||||
ListItem,
|
||||
GlButton,
|
||||
},
|
||||
propsData: {
|
||||
item,
|
||||
|
@ -44,6 +47,9 @@ describe('Image List Row', () => {
|
|||
},
|
||||
provide: {
|
||||
config: {},
|
||||
glFeatures: {
|
||||
...features,
|
||||
},
|
||||
},
|
||||
directives: {
|
||||
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: '' } });
|
||||
|
||||
expect(findDetailsLink().text()).toBe(item.path);
|
||||
|
@ -143,6 +149,35 @@ describe('Image List Row', () => {
|
|||
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', () => {
|
||||
|
|
|
@ -11,6 +11,10 @@ export const imagesListResponse = [
|
|||
createdAt: '2020-11-03T13:29:21Z',
|
||||
expirationPolicyStartedAt: null,
|
||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/22',
|
||||
path: 'gitlab-test',
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'ContainerRepository',
|
||||
|
@ -24,6 +28,10 @@ export const imagesListResponse = [
|
|||
createdAt: '2020-09-21T06:57:43Z',
|
||||
expirationPolicyStartedAt: null,
|
||||
expirationPolicyCleanupStatus: 'UNSCHEDULED',
|
||||
project: {
|
||||
id: 'gid://gitlab/Project/22',
|
||||
path: 'gitlab-test',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -72,14 +72,23 @@ describe('Test report extension', () => {
|
|||
});
|
||||
|
||||
describe('summary', () => {
|
||||
it('displays loading text', () => {
|
||||
it('displays loading state initially', () => {
|
||||
mockApi(httpStatusCodes.OK);
|
||||
createComponent();
|
||||
|
||||
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);
|
||||
createComponent();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
# Switch this back to `fast_spec_helper` when removing the `gitlab_sli_new_counters`
|
||||
# feature flag
|
||||
require 'spec_helper'
|
||||
require 'fast_spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Metrics::Sli do
|
||||
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
|
||||
apdex_counters = [
|
||||
fake_total_counter('foo_apdex'),
|
||||
fake_numerator_counter('foo_apdex', 'success'),
|
||||
fake_total_counter('foo_apdex', ':'),
|
||||
fake_numerator_counter('foo_apdex', 'success', ':')
|
||||
fake_numerator_counter('foo_apdex', 'success')
|
||||
]
|
||||
|
||||
error_rate_counters = [
|
||||
fake_total_counter('foo'),
|
||||
fake_numerator_counter('foo', 'error'),
|
||||
fake_total_counter('foo', ':'),
|
||||
fake_numerator_counter('foo', 'error', ':')
|
||||
fake_numerator_counter('foo', 'error')
|
||||
]
|
||||
|
||||
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
|
||||
counters = [
|
||||
fake_total_counter("bar#{subclass_info[:suffix]}"),
|
||||
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], ':')
|
||||
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
|
||||
]
|
||||
|
||||
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
|
||||
counters = [
|
||||
fake_total_counter("bar#{subclass_info[:suffix]}"),
|
||||
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], ':')
|
||||
fake_numerator_counter("bar#{subclass_info[:suffix]}", subclass_info[:numerator])
|
||||
]
|
||||
|
||||
sli = described_class.initialize_sli(:bar, [{ hello: :world }])
|
||||
|
@ -122,8 +112,6 @@ RSpec.describe Gitlab::Metrics::Sli do
|
|||
before do
|
||||
fake_total_counter("boom#{subclass_info[:suffix]}")
|
||||
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
|
||||
|
||||
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
|
||||
counters = [
|
||||
fake_total_counter("hey#{subclass_info[:suffix]}"),
|
||||
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], ':')
|
||||
fake_numerator_counter("hey#{subclass_info[:suffix]}", subclass_info[:numerator])
|
||||
]
|
||||
|
||||
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: 'baz' }))
|
||||
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
|
||||
|
||||
describe "#increment" do
|
||||
let!(:sli) { described_class.new(:heyo) }
|
||||
let!(:total_counter) { fake_total_counter("heyo#{subclass_info[:suffix]}") }
|
||||
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
|
||||
sli.increment(labels: { hello: "world" }, subclass_info[:numerator] => true)
|
||||
|
||||
expect(total_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
|
||||
|
||||
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(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
|
||||
|
|
Loading…
Reference in New Issue