diff --git a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
index b7d7e2bacc1..aecc0bf92ea 100644
--- a/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
+++ b/app/assets/javascripts/packages_and_registries/container_registry/explorer/components/list_page/image_list_row.vue
@@ -1,10 +1,12 @@
@@ -103,7 +135,20 @@ export default {
:disabled="deleting"
>
+
{
- 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,
},
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
index 7bbcb0cd04a..4ffd06de61f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/test_report/utils.js
@@ -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)
);
})
diff --git a/app/controllers/groups/registry/repositories_controller.rb b/app/controllers/groups/registry/repositories_controller.rb
index cb7bf001918..bb2d08e487a 100644
--- a/app/controllers/groups/registry/repositories_controller.rb
+++ b/app/controllers/groups/registry/repositories_controller.rb
@@ -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
diff --git a/app/controllers/projects/registry/repositories_controller.rb b/app/controllers/projects/registry/repositories_controller.rb
index ad3b2bc98e7..87cb8e4781f 100644
--- a/app/controllers/projects/registry/repositories_controller.rb
+++ b/app/controllers/projects/registry/repositories_controller.rb
@@ -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! }
diff --git a/app/graphql/queries/container_registry/get_container_repositories.query.graphql b/app/graphql/queries/container_registry/get_container_repositories.query.graphql
index 264878ccaa2..5f995eb958b 100644
--- a/app/graphql/queries/container_registry/get_container_repositories.query.graphql
+++ b/app/graphql/queries/container_registry/get_container_repositories.query.graphql
@@ -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 {
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index f3264f733ab..485b3a9828b 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -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')
diff --git a/config/feature_flags/development/container_registry_show_shortened_path.yml b/config/feature_flags/development/container_registry_show_shortened_path.yml
new file mode 100644
index 00000000000..33781386e8a
--- /dev/null
+++ b/config/feature_flags/development/container_registry_show_shortened_path.yml
@@ -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
diff --git a/config/feature_flags/development/gitlab_sli_new_counters.yml b/config/feature_flags/development/gitlab_sli_new_counters.yml
deleted file mode 100644
index 62d6b82b0db..00000000000
--- a/config/feature_flags/development/gitlab_sli_new_counters.yml
+++ /dev/null
@@ -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
diff --git a/lib/gitlab/metrics/sli.rb b/lib/gitlab/metrics/sli.rb
index 75734c792d5..15cfe777f4d 100644
--- a/lib/gitlab/metrics/sli.rb
+++ b/lib/gitlab/metrics/sli.rb
@@ -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
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7880d47acba..dc0a8d37c3e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -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 ""
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
index 301acf238e7..d12933526bc 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/components/list_page/image_list_row_spec.js
@@ -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', () => {
diff --git a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
index 7e6f88fe5bc..f9739509ef9 100644
--- a/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
+++ b/spec/frontend/packages_and_registries/container_registry/explorer/mock_data.js
@@ -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',
+ },
},
];
diff --git a/spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js
index da4b990c078..c09ed7dbc6c 100644
--- a/spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js
+++ b/spec/frontend/vue_mr_widget/extensions/test_report/index_spec.js
@@ -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();
diff --git a/spec/frontend/vue_mr_widget/extensions/test_report/utils_spec.js b/spec/frontend/vue_mr_widget/extensions/test_report/utils_spec.js
new file mode 100644
index 00000000000..69ea70549fe
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/extensions/test_report/utils_spec.js
@@ -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);
+ });
+ });
+});
diff --git a/spec/lib/gitlab/metrics/sli_spec.rb b/spec/lib/gitlab/metrics/sli_spec.rb
index 983b90b9f25..d100f66be19 100644
--- a/spec/lib/gitlab/metrics/sli_spec.rb
+++ b/spec/lib/gitlab/metrics/sli_spec.rb
@@ -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