Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b6927b3dd6
commit
fc45ff50c1
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
f52d88262879c40d9ac60a74853b7070036f244fd5f7957c59bbfceb343811d1
|
|
@ -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)),
|
||||
|
|
|
@ -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. |
|
||||
|
|
|
@ -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):
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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 ""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue