Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-04 06:18:17 +00:00
parent b6927b3dd6
commit fc45ff50c1
29 changed files with 245 additions and 54 deletions

View File

@ -2,6 +2,7 @@
import $ from 'jquery';
import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown';
import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js';
export default class TemplateSelector {
constructor({ dropdown, data, pattern, wrapper, editor, $input } = {}) {
@ -10,10 +11,9 @@ export default class TemplateSelector {
this.dropdown = dropdown;
this.$dropdownContainer = wrapper;
this.$filenameInput = $input || $('#file_name');
this.$dropdownIcon = $('.dropdown-menu-toggle-icon', dropdown);
this.$loadingIcon = $(
'<div class="gl-spinner gl-spinner-orange gl-spinner-sm gl-absolute gl-top-3 gl-right-3 gl-display-none"></div>',
).insertAfter(this.$dropdownIcon);
this.dropdownIcon = dropdown[0].querySelector('.dropdown-menu-toggle-icon');
this.loadingIcon = loadingIconForLegacyJS({ classes: ['gl-display-none'] });
this.dropdownIcon.parentNode.insertBefore(this.loadingIcon, this.dropdownIcon.nextSibling);
this.initDropdown(dropdown, data);
this.listenForFilenameInput();
@ -100,12 +100,12 @@ export default class TemplateSelector {
}
startLoadingSpinner() {
this.$loadingIcon.removeClass('gl-display-none');
this.$dropdownIcon.addClass('gl-display-none');
this.loadingIcon.classList.remove('gl-display-none');
this.dropdownIcon.classList.add('gl-display-none');
}
stopLoadingSpinner() {
this.$loadingIcon.addClass('gl-display-none');
this.$dropdownIcon.removeClass('gl-display-none');
this.loadingIcon.classList.add('gl-display-none');
this.dropdownIcon.classList.remove('gl-display-none');
}
}

View File

@ -68,7 +68,10 @@ export default {
data-toggle="dropdown"
>
<span class="dropdown-toggle-text">{{ __('Choose a template') }}</span>
<gl-icon name="chevron-down" class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500" />
<gl-icon
name="chevron-down"
class="gl-absolute gl-top-3 gl-right-3 gl-text-gray-500 dropdown-menu-toggle-icon"
/>
</button>
<div class="dropdown-menu dropdown-select">
<div class="dropdown-title gl-display-flex gl-justify-content-center">

View File

@ -224,6 +224,10 @@ export default {
}
return '';
},
onDeleted({ message }) {
this.$root.$toast?.show(message);
this.$apollo.queries.runners.refetch();
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
@ -282,7 +286,11 @@ export default {
</gl-link>
</template>
<template #runner-actions-cell="{ runner }">
<runner-actions-cell :runner="runner" :edit-url="runner.editAdminUrl" />
<runner-actions-cell
:runner="runner"
:edit-url="runner.editAdminUrl"
@deleted="onDeleted"
/>
</template>
</runner-list>
<runner-pagination

View File

@ -23,6 +23,7 @@ export default {
required: false,
},
},
emits: ['deleted'],
computed: {
canUpdate() {
return this.runner.userPermissions?.updateRunner;
@ -31,6 +32,11 @@ export default {
return this.runner.userPermissions?.deleteRunner;
},
},
methods: {
onDeleted(value) {
this.$emit('deleted', value);
},
},
};
</script>
@ -38,6 +44,6 @@ export default {
<gl-button-group>
<runner-edit-button v-if="canUpdate && editUrl" :href="editUrl" />
<runner-pause-button v-if="canUpdate" :runner="runner" :compact="true" />
<runner-delete-button v-if="canDelete" :runner="runner" :compact="true" />
<runner-delete-button v-if="canDelete" :runner="runner" :compact="true" @deleted="onDeleted" />
</gl-button-group>
</template>

View File

@ -2,14 +2,12 @@
import { GlButton, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
import runnerDeleteMutation from '~/runner/graphql/shared/runner_delete.mutation.graphql';
import { createAlert } from '~/flash';
import { s__, sprintf } from '~/locale';
import { sprintf } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { I18N_DELETE_RUNNER } from '../constants';
import { I18N_DELETE_RUNNER, I18N_DELETED_TOAST } from '../constants';
import RunnerDeleteModal from './runner_delete_modal.vue';
const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted');
export default {
name: 'RunnerDeleteButton',
components: {
@ -34,6 +32,7 @@ export default {
default: false,
},
},
emits: ['deleted'],
data() {
return {
deleting: false,
@ -102,12 +101,13 @@ export default {
id: this.runner.id,
},
},
refetchQueries: ['getRunners', 'getGroupRunners'],
});
if (errors && errors.length) {
throw new Error(errors.join(' '));
} else {
this.$root.$toast?.show(sprintf(I18N_DELETED_TOAST, { name: this.runnerName }));
this.$emit('deleted', {
message: sprintf(I18N_DELETED_TOAST, { name: this.runnerName }),
});
}
} catch (e) {
this.deleting = false;

View File

@ -40,6 +40,7 @@ export const I18N_EDIT = __('Edit');
export const I18N_PAUSE = __('Pause');
export const I18N_RESUME = __('Resume');
export const I18N_DELETE_RUNNER = s__('Runners|Delete runner');
export const I18N_DELETED_TOAST = s__('Runners|Runner %{name} was deleted');
export const I18N_LOCKED_RUNNER_DESCRIPTION = s__('Runners|You cannot assign to other projects');
export const I18N_PAUSED_RUNNER_DESCRIPTION = s__('Runners|Not available to run jobs');

View File

@ -16,13 +16,13 @@ import RunnerActionsCell from '../components/cells/runner_actions_cell.vue';
import { statusTokenConfig } from '../components/search_tokens/status_token_config';
import {
I18N_FETCH_ERROR,
GROUP_FILTERED_SEARCH_NAMESPACE,
GROUP_TYPE,
PROJECT_TYPE,
STATUS_ONLINE,
STATUS_OFFLINE,
STATUS_STALE,
I18N_FETCH_ERROR,
} from '../constants';
import groupRunnersQuery from '../graphql/list/group_runners.query.graphql';
import groupRunnersCountQuery from '../graphql/list/group_runners_count.query.graphql';
@ -241,6 +241,10 @@ export default {
editUrl(runner) {
return this.runners.urlsById[runner.id]?.edit;
},
onDeleted({ message }) {
this.$root.$toast?.show(message);
this.$apollo.queries.runners.refetch();
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
@ -298,7 +302,7 @@ export default {
</gl-link>
</template>
<template #runner-actions-cell="{ runner }">
<runner-actions-cell :runner="runner" :edit-url="editUrl(runner)" />
<runner-actions-cell :runner="runner" :edit-url="editUrl(runner)" @deleted="onDeleted" />
</template>
</runner-list>
<runner-pagination

View File

@ -212,6 +212,7 @@ module ApplicationSettingsHelper
:auto_devops_enabled,
:auto_devops_domain,
:container_expiration_policies_enable_historic_entries,
:container_registry_expiration_policies_caching,
:container_registry_token_expire_delay,
:default_artifacts_expire_in,
:default_branch_name,

View File

@ -362,6 +362,9 @@ class ApplicationSetting < ApplicationRecord
:container_registry_expiration_policies_worker_capacity,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
validates :container_registry_expiration_policies_caching,
inclusion: { in: [true, false], message: _('must be a boolean value') }
validates :container_registry_import_max_tags_count,
:container_registry_import_max_retries,
:container_registry_import_start_max_retries,

View File

@ -220,6 +220,7 @@ module ApplicationSettingImplementation
container_registry_delete_tags_service_timeout: 250,
container_registry_expiration_policies_worker_capacity: 4,
container_registry_cleanup_tags_service_max_list_size: 200,
container_registry_expiration_policies_caching: true,
container_registry_import_max_tags_count: 100,
container_registry_import_max_retries: 3,
container_registry_import_start_max_retries: 50,

View File

@ -105,10 +105,7 @@ module Ci
end
def refspec_for_persistent_ref
# Use persistent_ref.sha because it sometimes causes 'git fetch' to do
# less work. See
# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/746.
"+#{pipeline.persistent_ref.sha}:#{pipeline.persistent_ref.path}"
"+#{pipeline.persistent_ref.path}:#{pipeline.persistent_ref.path}"
end
def persistent_ref_exist?

View File

@ -145,8 +145,10 @@ module Projects
end
def caching_enabled?
container_expiration_policy &&
older_than.present?
result = ::Gitlab::CurrentSettings.current_application_settings.container_registry_expiration_policies_caching &&
container_expiration_policy &&
older_than.present?
!!result
end
def throttling_enabled?

View File

@ -30,5 +30,13 @@
= f.number_field :container_registry_cleanup_tags_service_max_list_size, min: 0, class: 'form-control'
.form-text.text-muted
= _("The maximum number of tags that a single worker accepts for cleanup. If the number of tags goes above this limit, the list of tags to delete is truncated to this number. To remove this limit, set it to 0.")
.form-group
.form-check
= f.check_box :container_registry_expiration_policies_caching, class: 'form-check-input'
= f.label :container_registry_expiration_policies_caching, class: 'form-check-label' do
= _("Enable container expiration caching.")
.form-text.text-muted
= _("When enabled, cleanup polices execute faster but put more load on Redis.")
= link_to sprite_icon('question-o'), help_page_path('user/packages/container_registry/reduce_container_registry_storage', anchor: 'set-cleanup-limits-to-conserve-resources')
= f.submit _('Save changes'), class: "gl-button btn btn-confirm"

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddContainerRegistryExpirationPoliciesCachingToApplicationSettings < Gitlab::Database::Migration[1.0]
enable_lock_retries!
def up
add_column :application_settings, :container_registry_expiration_policies_caching, :boolean, null: false, default: true
end
def down
remove_column :application_settings, :container_registry_expiration_policies_caching
end
end

View File

@ -0,0 +1 @@
f52d88262879c40d9ac60a74853b7070036f244fd5f7957c59bbfceb343811d1

View File

@ -11250,6 +11250,7 @@ CREATE TABLE application_settings (
ed25519_sk_key_restriction integer DEFAULT 0 NOT NULL,
users_get_by_id_limit integer DEFAULT 300 NOT NULL,
users_get_by_id_limit_allowlist text[] DEFAULT '{}'::text[] NOT NULL,
container_registry_expiration_policies_caching boolean DEFAULT true NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),

View File

@ -53,6 +53,10 @@ Example response:
"gravatar_enabled" : true,
"sign_in_text" : null,
"container_expiration_policies_enable_historic_entries": true,
"container_registry_cleanup_tags_service_max_list_size": 200,
"container_registry_delete_tags_service_timeout": 250,
"container_registry_expiration_policies_caching": true,
"container_registry_expiration_policies_worker_capacity": 4,
"container_registry_token_expire_delay": 5,
"repository_storages_weighted": {"default": 100},
"plantuml_enabled": false,
@ -158,6 +162,11 @@ Example response:
"external_authorization_service_timeout": 0.5,
"user_oauth_applications": true,
"after_sign_out_path": "",
"container_expiration_policies_enable_historic_entries": true,
"container_registry_cleanup_tags_service_max_list_size": 200,
"container_registry_delete_tags_service_timeout": 250,
"container_registry_expiration_policies_caching": true,
"container_registry_expiration_policies_worker_capacity": 4,
"container_registry_token_expire_delay": 5,
"repository_storages": ["default"],
"plantuml_enabled": false,
@ -248,6 +257,11 @@ listed in the descriptions of the relevant settings.
| `automatic_purchased_storage_allocation` | boolean | no | Enabling this permits automatic allocation of purchased storage in a namespace. |
| `check_namespace_plan` **(PREMIUM)** | boolean | no | Enabling this makes only licensed EE features available to projects if the project namespace's plan includes the feature or if the project is public. |
| `commit_email_hostname` | string | no | Custom hostname (for private commit emails). |
| `container_expiration_policies_enable_historic_entries` | boolean | no | Enable [cleanup policies](../user/packages/container_registry/reduce_container_registry_storage.md#enable-the-cleanup-policy) for all projects. |
| `container_registry_cleanup_tags_service_max_list_size` | integer | no | The maximum number of tags that can be deleted in a single execution of [cleanup policies](../user/packages/container_registry/reduce_container_registry_storage.md#set-cleanup-limits-to-conserve-resources). |
| `container_registry_delete_tags_service_timeout` | integer | no | The maximum time, in seconds, that the cleanup process can take to delete a batch of tags for [cleanup policies](../user/packages/container_registry/reduce_container_registry_storage.md#set-cleanup-limits-to-conserve-resources). |
| `container_registry_expiration_policies_caching` | boolean | no | Caching during the execution of [cleanup policies](../user/packages/container_registry/reduce_container_registry_storage.md#set-cleanup-limits-to-conserve-resources). |
| `container_registry_expiration_policies_worker_capacity` | integer | no | Number of workers for [cleanup policies](../user/packages/container_registry/reduce_container_registry_storage.md#set-cleanup-limits-to-conserve-resources). |
| `container_registry_token_expire_delay` | integer | no | Container Registry token duration in minutes. |
| `deactivate_dormant_users` | boolean | no | Enable [automatic deactivation of dormant users](../user/admin_area/moderate_users.md#automatically-deactivate-dormant-users). |
| `default_artifacts_expire_in` | string | no | Set the default expiration time for each job's artifacts. |

View File

@ -192,6 +192,9 @@ To prevent server resource starvation, the following application settings are av
deleted in a single execution. Additional tags must be deleted in another execution. We recommend
starting with a low number and increasing it after monitoring that container images are properly
deleted. The default value is `200`.
- `container_registry_expiration_policies_caching`: enable or disable tag creation timestamp caching
during execution of policies. Cached timestamps are stored in [Redis](../../../development/architecture.md#redis).
Enabled by default.
For self-managed instances, those settings can be updated in the [Rails console](../../../administration/operations/rails_console.md#starting-a-rails-console-session):

View File

@ -99,6 +99,7 @@ Each user's access is based on:
Prerequisite:
- You must have the Maintainer or Owner role.
- Sharing the project with other groups must not be [prevented](../../group/index.md#prevent-a-project-from-being-shared-with-groups).
To add groups to a project:

View File

@ -13562,6 +13562,9 @@ msgstr ""
msgid "Enable container expiration and retention policies for projects created earlier than GitLab 12.7."
msgstr ""
msgid "Enable container expiration caching."
msgstr ""
msgid "Enable delayed project deletion by default for newly-created groups."
msgstr ""
@ -41392,6 +41395,9 @@ msgstr ""
msgid "When enabled, SSH keys with no expiry date or an invalid expiration date are no longer accepted. Leave blank for no limit."
msgstr ""
msgid "When enabled, cleanup polices execute faster but put more load on Redis."
msgstr ""
msgid "When enabled, existing personal access tokens may be revoked. Leave blank for no limit."
msgstr ""

View File

@ -373,7 +373,8 @@ RSpec.describe 'Admin updates settings' do
{
container_registry_delete_tags_service_timeout: 'Container Registry delete tags service execution timeout',
container_registry_expiration_policies_worker_capacity: 'Cleanup policy maximum workers running concurrently',
container_registry_cleanup_tags_service_max_list_size: 'Cleanup policy maximum number of tags to be deleted'
container_registry_cleanup_tags_service_max_list_size: 'Cleanup policy maximum number of tags to be deleted',
container_registry_expiration_policies_caching: 'Enable container expiration caching'
}
end
@ -422,6 +423,38 @@ RSpec.describe 'Admin updates settings' do
end
end
end
context 'for container registry setting container_registry_expiration_policies_caching' do
context 'with feature flag enabled' do
context 'with client supporting tag delete' do
it 'updates container_registry_expiration_policies_caching' do
old_value = current_settings.container_registry_expiration_policies_caching
visit ci_cd_admin_application_settings_path
page.within('.as-registry') do
find('#application_setting_container_registry_expiration_policies_caching.form-check-input').click
click_button 'Save changes'
end
expect(current_settings.container_registry_expiration_policies_caching).to eq(!old_value)
expect(page).to have_content "Application settings saved successfully"
end
end
context 'with client not supporting tag delete' do
let(:client_support) { false }
it_behaves_like 'not having container registry setting', :container_registry_expiration_policies_caching
end
end
context 'with feature flag disabled' do
let(:feature_flag_enabled) { false }
it_behaves_like 'not having container registry setting', :container_registry_expiration_policies_caching
end
end
end
end

View File

@ -1,5 +1,5 @@
import Vue from 'vue';
import { GlLink } from '@gitlab/ui';
import { GlToast, GlLink } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
@ -18,8 +18,8 @@ import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
import {
@ -52,6 +52,7 @@ jest.mock('~/lib/utils/url_utility', () => ({
}));
Vue.use(VueApollo);
Vue.use(GlToast);
describe('AdminRunnersApp', () => {
let wrapper;
@ -59,6 +60,7 @@ describe('AdminRunnersApp', () => {
let mockRunnersCountQuery;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
const findRunnerList = () => wrapper.findComponent(RunnerList);
@ -95,6 +97,7 @@ describe('AdminRunnersApp', () => {
afterEach(() => {
mockRunnersQuery.mockReset();
mockRunnersCountQuery.mockReset();
wrapper.destroy();
});
@ -228,6 +231,41 @@ describe('AdminRunnersApp', () => {
]);
});
describe('Single runner row', () => {
let showToast;
const mockRunner = runnersData.data.runners.nodes[0];
const { id: graphqlId, shortSha } = mockRunner;
const id = getIdFromGraphQLId(graphqlId);
beforeEach(async () => {
mockRunnersQuery.mockClear();
createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
await waitForPromises();
});
it('Links to the runner page', async () => {
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink);
expect(runnerLink.text()).toBe(`#${id} (${shortSha})`);
expect(runnerLink.attributes('href')).toBe(`http://localhost/admin/runners/${id}`);
});
it('When runner is deleted, data is refetched and a toast message is shown', async () => {
expect(mockRunnersQuery).toHaveBeenCalledTimes(1);
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(mockRunnersQuery).toHaveBeenCalledTimes(2);
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Runner deleted');
});
});
describe('when a filter is preselected', () => {
beforeEach(async () => {
setWindowLocation(`?status[]=${STATUS_ACTIVE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`);

View File

@ -92,6 +92,18 @@ describe('RunnerActionsCell', () => {
expect(findDeleteBtn().props('compact')).toBe(true);
});
it('Emits delete events', () => {
const value = { name: 'Runner' };
createComponent();
expect(wrapper.emitted('deleted')).toBe(undefined);
findDeleteBtn().vm.$emit('deleted', value);
expect(wrapper.emitted('deleted')).toEqual([[value]]);
});
it('Does not render the runner delete button when user cannot delete', () => {
createComponent({
runner: {

View File

@ -1,5 +1,5 @@
import Vue from 'vue';
import { GlButton, GlToast } from '@gitlab/ui';
import { GlButton } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
@ -19,7 +19,6 @@ const mockRunner = runnersData.data.runners.nodes[0];
const mockRunnerId = getIdFromGraphQLId(mockRunner.id);
Vue.use(VueApollo);
Vue.use(GlToast);
jest.mock('~/flash');
jest.mock('~/runner/sentry_utils');
@ -27,7 +26,6 @@ jest.mock('~/runner/sentry_utils');
describe('RunnerDeleteButton', () => {
let wrapper;
let runnerDeleteHandler;
let showToast;
const getTooltip = () => getBinding(wrapper.element, 'gl-tooltip').value;
const getModal = () => getBinding(wrapper.element, 'gl-modal').value;
@ -52,8 +50,6 @@ describe('RunnerDeleteButton', () => {
GlModal: createMockDirective(),
},
});
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show').mockImplementation(() => {});
};
const clickOkAndWait = async () => {
@ -128,7 +124,7 @@ describe('RunnerDeleteButton', () => {
await clickOkAndWait();
});
it('The mutation to delete is called', async () => {
it('The mutation to delete is called', () => {
expect(runnerDeleteHandler).toHaveBeenCalledTimes(1);
expect(runnerDeleteHandler).toHaveBeenCalledWith({
input: {
@ -137,8 +133,12 @@ describe('RunnerDeleteButton', () => {
});
});
it('The user is notified', async () => {
expect(showToast).toHaveBeenCalledTimes(1);
it('The user can be notified with an event', () => {
const deleted = wrapper.emitted('deleted');
expect(deleted).toHaveLength(1);
expect(deleted[0][0].message).toMatch(`#${mockRunnerId}`);
expect(deleted[0][0].message).toMatch(`${mockRunner.shortSha}`);
});
});

View File

@ -1,5 +1,5 @@
import Vue, { nextTick } from 'vue';
import { GlButton, GlLink } from '@gitlab/ui';
import { GlButton, GlLink, GlToast } from '@gitlab/ui';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
@ -17,6 +17,7 @@ import RunnerTypeTabs from '~/runner/components/runner_type_tabs.vue';
import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_bar.vue';
import RunnerList from '~/runner/components/runner_list.vue';
import RunnerStats from '~/runner/components/stat/runner_stats.vue';
import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue';
import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue';
import RunnerPagination from '~/runner/components/runner_pagination.vue';
@ -40,6 +41,7 @@ import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered
import { groupRunnersData, groupRunnersDataPaginated, groupRunnersCountData } from '../mock_data';
Vue.use(VueApollo);
Vue.use(GlToast);
const mockGroupFullPath = 'group1';
const mockRegistrationToken = 'AABBCC';
@ -59,6 +61,7 @@ describe('GroupRunnersApp', () => {
let mockGroupRunnersCountQuery;
const findRunnerStats = () => wrapper.findComponent(RunnerStats);
const findRunnerActionsCell = () => wrapper.findComponent(RunnerActionsCell);
const findRegistrationDropdown = () => wrapper.findComponent(RegistrationDropdown);
const findRunnerTypeTabs = () => wrapper.findComponent(RunnerTypeTabs);
const findRunnerList = () => wrapper.findComponent(RunnerList);
@ -187,12 +190,17 @@ describe('GroupRunnersApp', () => {
});
describe('Single runner row', () => {
let showToast;
const { webUrl, editUrl, node } = mockGroupRunnersEdges[0];
const { id: graphqlId, shortSha } = node;
const id = getIdFromGraphQLId(graphqlId);
beforeEach(async () => {
mockGroupRunnersQuery.mockClear();
createComponent({ mountFn: mountExtended });
showToast = jest.spyOn(wrapper.vm.$root.$toast, 'show');
await waitForPromises();
});
@ -212,6 +220,17 @@ describe('GroupRunnersApp', () => {
href: editUrl,
});
});
it('When runner is deleted, data is refetched and a toast is shown', async () => {
expect(mockGroupRunnersQuery).toHaveBeenCalledTimes(1);
findRunnerActionsCell().vm.$emit('deleted', { message: 'Runner deleted' });
expect(mockGroupRunnersQuery).toHaveBeenCalledTimes(2);
expect(showToast).toHaveBeenCalledTimes(1);
expect(showToast).toHaveBeenCalledWith('Runner deleted');
});
});
describe('when a filter is preselected', () => {

View File

@ -76,6 +76,8 @@ RSpec.describe ApplicationSetting do
it { is_expected.to validate_numericality_of(:container_registry_delete_tags_service_timeout).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:container_registry_cleanup_tags_service_max_list_size).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:container_registry_expiration_policies_worker_capacity).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to allow_value(true).for(:container_registry_expiration_policies_caching) }
it { is_expected.to allow_value(false).for(:container_registry_expiration_policies_caching) }
it { is_expected.to validate_numericality_of(:container_registry_import_max_tags_count).only_integer.is_greater_than_or_equal_to(0) }
it { is_expected.to validate_numericality_of(:container_registry_import_max_retries).only_integer.is_greater_than_or_equal_to(0) }

View File

@ -173,11 +173,7 @@ RSpec.describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do
is_expected.to contain_exactly("+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}",
"+#{pipeline.sha}:refs/pipelines/#{pipeline.id}")
end
it 'uses a SHA in the persistent refspec' do
expect(subject[0]).to match(%r{^\+[0-9a-f]{40}:refs/pipelines/[0-9]+$})
"+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}")
end
context 'when ref is tag' do
@ -185,7 +181,7 @@ RSpec.describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do
is_expected.to contain_exactly("+refs/tags/#{build.ref}:refs/tags/#{build.ref}",
"+#{pipeline.sha}:refs/pipelines/#{pipeline.id}")
"+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}")
end
context 'when GIT_DEPTH is zero' do
@ -196,7 +192,7 @@ RSpec.describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do
is_expected.to contain_exactly('+refs/tags/*:refs/tags/*',
'+refs/heads/*:refs/remotes/origin/*',
"+#{pipeline.sha}:refs/pipelines/#{pipeline.id}")
"+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}")
end
end
end
@ -212,7 +208,7 @@ RSpec.describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do
is_expected
.to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}")
.to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}")
end
context 'when GIT_DEPTH is zero' do
@ -222,7 +218,7 @@ RSpec.describe Ci::BuildRunnerPresenter do
it 'returns the correct refspecs' do
is_expected
.to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
.to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
'+refs/heads/*:refs/remotes/origin/*',
'+refs/tags/*:refs/tags/*')
end
@ -232,7 +228,7 @@ RSpec.describe Ci::BuildRunnerPresenter do
let(:merge_request) { create(:merge_request, :with_legacy_detached_merge_request_pipeline) }
it 'returns the correct refspecs' do
is_expected.to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
is_expected.to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
"+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
end
@ -250,7 +246,7 @@ RSpec.describe Ci::BuildRunnerPresenter do
it 'exposes the persistent pipeline ref' do
is_expected
.to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
.to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
"+refs/heads/#{build.ref}:refs/remotes/origin/#{build.ref}")
end
end

View File

@ -156,7 +156,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
'sha' => job.sha,
'before_sha' => job.before_sha,
'ref_type' => 'branch',
'refspecs' => ["+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
'refspecs' => ["+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
"+refs/heads/#{job.ref}:refs/remotes/origin/#{job.ref}"],
'depth' => project.ci_default_git_depth }
end
@ -291,7 +291,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['git_info']['refspecs'])
.to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
.to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
'+refs/tags/*:refs/tags/*',
'+refs/heads/*:refs/remotes/origin/*')
end
@ -359,7 +359,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:created)
expect(json_response['git_info']['refspecs'])
.to contain_exactly("+#{pipeline.sha}:refs/pipelines/#{pipeline.id}",
.to contain_exactly("+refs/pipelines/#{pipeline.id}:refs/pipelines/#{pipeline.id}",
'+refs/tags/*:refs/tags/*',
'+refs/heads/*:refs/remotes/origin/*')
end

View File

@ -267,12 +267,30 @@ RSpec.describe Projects::ContainerRepository::CleanupTagsService, :clean_gitlab_
'container_expiration_policy' => true }
end
it 'succeeds without a user' do
before do
expect_delete(%w(Bb Ba C), container_expiration_policy: true)
end
expect_caching
it { is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3)) }
is_expected.to eq(expected_service_response(deleted: %w(Bb Ba C), before_delete_size: 3))
context 'caching' do
it 'expects caching to be used' do
expect_caching
subject
end
context 'when setting set to false' do
before do
stub_application_setting(container_registry_expiration_policies_caching: false)
end
it 'does not use caching' do
expect_no_caching
subject
end
end
end
end