Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-11-21 03:25:07 +00:00
parent e797ca243f
commit af9648ecad
27 changed files with 255 additions and 107 deletions

View File

@ -1 +1 @@
46c6386bf37c16b85c99bc7502fd696579893b03
ccbba5a4c00e02b85995b6ad75ed5fa3274ccaea

View File

@ -105,7 +105,10 @@ export default {
</script>
<template>
<div class="container-fluid gl-my-1 gl-grid-rows-auto">
<div class="row gl-my-3 gl-flex gl-items-center" data-testid="widget-row">
<div
class="row gl-my-3 gl-flex gl-flex-wrap gl-items-center gl-gap-y-4"
data-testid="widget-row"
>
<div class="align-items-center col-4 gl-flex gl-text-gray-900">
<ci-icon :status="job.detailedStatus" />
<gl-link
@ -124,9 +127,8 @@ export default {
<gl-tooltip v-if="!canRetryJob" :target="() => $refs.retryBtn" placement="top">
{{ tooltipErrorText }}
</gl-tooltip>
<div class="col-4 gl-text-right">
<div class="col-4 gl-flex gl-max-w-full gl-flex-grow gl-justify-end gl-gap-3">
<root-cause-analysis-button
class="gl-mr-2"
:job-gid="job.id"
:job-status-group="statusGroup"
:can-troubleshoot-job="canTroubleshootJob"

View File

@ -817,3 +817,12 @@ export const isValidDateString = (dateString) => {
}
return !Number.isNaN(Date.parse(isoFormatted));
};
/**
* Converts the given number of days to seconds.
*
* @param {number} days Number of days to convert
*
* @returns {number} The equivalent number of seconds
*/
export const daysToSeconds = (days) => SECONDS_IN_DAY * days;

View File

@ -2,9 +2,9 @@
import { GlTable, GlLink, GlSprintf, GlIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import { containerRegistryPopover } from '~/usage_quotas/storage/constants';
import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
import HelpPageLink from '~/vue_shared/components/help_page_link/help_page_link.vue';
import { helpPagePath } from '~/helpers/help_page_helper';
import StorageTypeHelpLink from './storage_type_help_link.vue';
import StorageTypeWarning from './storage_type_warning.vue';
@ -94,7 +94,10 @@ export default {
return project.statistics.storageSize !== project.statistics.costFactoredStorageSize;
},
},
containerRegistryPopover,
containerRegistryDocsLink: helpPagePath(
'user/packages/container_registry/reduce_container_registry_storage.html',
{ anchor: 'view-container-registry-usage' },
),
};
</script>
@ -121,8 +124,12 @@ export default {
:storage-type="field.key"
:help-links="helpLinks"
/><storage-type-warning v-if="field.key == 'containerRegistry'">
{{ $options.containerRegistryPopover.content }}
<gl-link :href="$options.containerRegistryPopover.docsLink" target="_blank">
{{
s__(
'UsageQuotas|Container Registry storage statistics are not used to calculate the total project storage. Total project storage is calculated after namespace container deduplication, where the total of all unique containers is added to the namespace storage total.',
)
}}
<gl-link :href="$options.containerRegistryDocsLink" target="_blank">
{{ __('Learn more.') }}
</gl-link>
</storage-type-warning>

View File

@ -1,7 +1,6 @@
<script>
import { GlLink, GlIcon } from '@gitlab/ui';
import { sprintf } from '~/locale';
import { HELP_LINK_ARIA_LABEL } from '~/usage_quotas/storage/constants';
import { sprintf, s__ } from '~/locale';
export default {
name: 'StorageTypeHelpLink',
@ -21,7 +20,7 @@ export default {
},
computed: {
ariaLabel() {
return sprintf(HELP_LINK_ARIA_LABEL, {
return sprintf(s__('UsageQuota|%{linkTitle} help link'), {
linkTitle: this.storageType,
});
},

View File

@ -1,35 +1,6 @@
import { s__, __ } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
export const ERROR_MESSAGE = s__(
'UsageQuota|Something went wrong while fetching project storage statistics',
);
export const LEARN_MORE_LABEL = __('Learn more.');
export const USAGE_QUOTAS_LABEL = s__('UsageQuota|Usage Quotas');
export const TOTAL_USAGE_TITLE = s__('UsageQuota|Usage breakdown');
export const TOTAL_USAGE_SUBTITLE = s__(
'UsageQuota|Includes artifacts, repositories, wiki, and other items.',
);
export const TOTAL_USAGE_DEFAULT_TEXT = __('Not applicable.');
export const HELP_LINK_ARIA_LABEL = s__('UsageQuota|%{linkTitle} help link');
export const RECALCULATE_REPOSITORY_LABEL = s__('UsageQuota|Recalculate repository usage');
export const containerRegistryId = 'containerRegistrySize';
export const containerRegistryPopoverId = 'container-registry-popover';
export const containerRegistryPopover = {
content: s__(
'UsageQuotas|Container Registry storage statistics are not used to calculate the total project storage. Total project storage is calculated after namespace container deduplication, where the total of all unique containers is added to the namespace storage total.',
),
docsLink: helpPagePath(
'user/packages/container_registry/reduce_container_registry_storage.html',
{ anchor: 'view-container-registry-usage' },
),
};
export const PROJECT_TABLE_LABEL_STORAGE_TYPE = s__('UsageQuota|Storage type');
export const PROJECT_TABLE_LABEL_USAGE = s__('UsageQuota|Usage');
export const usageQuotasHelpPaths = {
repositorySizeLimit: helpPagePath('administration/settings/account_and_limit_settings', {
anchor: 'repository-size-limit',
@ -84,7 +55,9 @@ export const NAMESPACE_STORAGE_TYPES = [
`UsageQuota|Gitlab-integrated Docker Container Registry for storing Docker Images.`,
),
warning: {
popoverContent: containerRegistryPopover.content,
popoverContent: s__(
'UsageQuotas|Container Registry storage statistics are not used to calculate the total project storage. Total project storage is calculated after namespace container deduplication, where the total of all unique containers is added to the namespace storage total.',
),
},
},
];

View File

@ -1,6 +1,6 @@
<script>
import { GlAlert, GlButton, GlLink, GlLoadingIcon } from '@gitlab/ui';
import { getPreferredLocales, sprintf } from '~/locale';
import { getPreferredLocales, sprintf, s__, __ } from '~/locale';
import { updateRepositorySize } from '~/api/projects_api';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import SectionedPercentageBar from '~/usage_quotas/components/sectioned_percentage_bar.vue';
@ -8,14 +8,6 @@ import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/project/queries/project_storage.query.graphql';
import getCostFactoredProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/project/queries/cost_factored_project_storage.query.graphql';
import {
ERROR_MESSAGE,
LEARN_MORE_LABEL,
USAGE_QUOTAS_LABEL,
TOTAL_USAGE_TITLE,
TOTAL_USAGE_SUBTITLE,
TOTAL_USAGE_DEFAULT_TEXT,
HELP_LINK_ARIA_LABEL,
RECALCULATE_REPOSITORY_LABEL,
PROJECT_STORAGE_TYPES,
NAMESPACE_STORAGE_TYPES,
usageQuotasHelpPaths,
@ -49,7 +41,9 @@ export default {
};
},
error() {
this.error = ERROR_MESSAGE;
this.error = s__(
'UsageQuota|Something went wrong while fetching project storage statistics',
);
},
},
},
@ -69,7 +63,7 @@ export default {
return numberToHumanSize(this.project?.statistics?.storageSize, 1, getPreferredLocales());
}
return TOTAL_USAGE_DEFAULT_TEXT;
return __('Not applicable.');
},
projectStorageTypes() {
if (this.isStatisticsEmpty) {
@ -162,7 +156,7 @@ export default {
this.error = '';
},
helpLinkAriaLabel(linkTitle) {
return sprintf(HELP_LINK_ARIA_LABEL, {
return sprintf(s__('UsageQuota|%{linkTitle} help link'), {
linkTitle,
});
},
@ -178,11 +172,6 @@ export default {
},
},
usageQuotasHelpPaths,
LEARN_MORE_LABEL,
USAGE_QUOTAS_LABEL,
TOTAL_USAGE_TITLE,
TOTAL_USAGE_SUBTITLE,
RECALCULATE_REPOSITORY_LABEL,
};
</script>
<template>
@ -194,14 +183,14 @@ export default {
<div class="gl-pt-5">
<div class="gl-flex gl-justify-between">
<div>
<h4 class="gl-mb-3 gl-mt-0 gl-text-lg">{{ $options.TOTAL_USAGE_TITLE }}</h4>
<h4 class="gl-mb-3 gl-mt-0 gl-text-lg">{{ s__('UsageQuota|Usage breakdown') }}</h4>
<p>
{{ $options.TOTAL_USAGE_SUBTITLE }}
{{ s__('UsageQuota|Includes artifacts, repositories, wiki, and other items.') }}
<gl-link
:href="$options.usageQuotasHelpPaths.usageQuotas"
target="_blank"
:aria-label="helpLinkAriaLabel($options.USAGE_QUOTAS_LABEL)"
>{{ $options.LEARN_MORE_LABEL }}</gl-link
:aria-label="helpLinkAriaLabel(s__('UsageQuota|Usage Quotas'))"
>{{ __('Learn more.') }}</gl-link
>
</p>
</div>
@ -222,7 +211,7 @@ export default {
category="secondary"
@click="postRecalculateSize"
>
{{ $options.RECALCULATE_REPOSITORY_LABEL }}
{{ s__('UsageQuota|Recalculate repository usage') }}
</gl-button>
</div>
<project-storage-detail

View File

@ -1,12 +1,7 @@
<script>
import { GlIcon, GlLink, GlSprintf, GlTableLite, GlPopover } from '@gitlab/ui';
import NumberToHumanSize from '~/vue_shared/components/number_to_human_size/number_to_human_size.vue';
import { sprintf } from '~/locale';
import {
HELP_LINK_ARIA_LABEL,
PROJECT_TABLE_LABEL_STORAGE_TYPE,
PROJECT_TABLE_LABEL_USAGE,
} from '../../constants';
import { sprintf, s__ } from '~/locale';
import StorageTypeIcon from './storage_type_icon.vue';
export default {
@ -28,7 +23,7 @@ export default {
},
methods: {
helpLinkAriaLabel(linkTitle) {
return sprintf(HELP_LINK_ARIA_LABEL, {
return sprintf(s__('UsageQuota|%{linkTitle} help link'), {
linkTitle,
});
},
@ -36,12 +31,12 @@ export default {
projectTableFields: [
{
key: 'storageType',
label: PROJECT_TABLE_LABEL_STORAGE_TYPE,
label: s__('UsageQuota|Storage type'),
thClass: 'gl-w-9/10',
},
{
key: 'value',
label: PROJECT_TABLE_LABEL_USAGE,
label: s__('UsageQuota|Usage'),
thClass: 'gl-w-1/10',
},
],

View File

@ -167,13 +167,13 @@ export const EXTENSION_ICON_NAMES = {
};
export const EXTENSION_ICON_CLASS = {
failed: 'gl-text-red-500',
warning: 'gl-text-orange-500',
success: 'gl-text-green-500',
neutral: 'gl-text-gray-400',
error: 'gl-text-red-500',
notice: 'gl-text-gray-500',
scheduled: 'gl-text-blue-500',
failed: 'mr-widget-status-icon-failed',
warning: 'mr-widget-status-icon-warning',
success: 'mr-widget-status-icon-success',
neutral: 'mr-widget-status-icon-neutral',
error: 'mr-widget-status-icon-error',
notice: 'mr-widget-status-icon-notice',
scheduled: 'mr-widget-status-icon-scheduled',
severityCritical: 'gl-text-red-800',
severityHigh: 'gl-text-red-600',
severityMedium: 'gl-text-orange-400',

View File

@ -111,6 +111,11 @@ export default {
required: false,
default: null,
},
stickyFormSubmit: {
type: Boolean,
required: false,
default: false,
},
relatedItem: {
type: Object,
required: false,
@ -708,7 +713,11 @@ export default {
@error="$emit('error', $event)"
/>
</aside>
<div class="gl-col-start-1 gl-flex gl-gap-3 gl-py-3">
<div
v-if="!stickyFormSubmit"
class="gl-col-start-1 gl-flex gl-gap-3 gl-py-3"
data-testid="form-buttons"
>
<gl-button
variant="confirm"
:loading="loading"
@ -722,6 +731,25 @@ export default {
</gl-button>
</div>
</div>
<!-- stick to bottom and put the Confim button on the right -->
<!-- bg-overlap to match modal bg -->
<div
v-if="stickyFormSubmit"
class="gl-border-t gl-sticky gl-bottom-0 gl-z-1 -gl-mx-5 gl-flex gl-justify-end gl-gap-3 gl-bg-overlap gl-px-5 gl-py-3"
data-testid="form-buttons"
>
<gl-button type="button" data-testid="cancel-button" @click="handleCancelClick">
{{ __('Cancel') }}
</gl-button>
<gl-button
variant="confirm"
:loading="loading"
data-testid="create-button"
@click="createWorkItem"
>
{{ createWorkItemText }}
</gl-button>
</div>
</div>
</template>
</form>

View File

@ -236,7 +236,9 @@ export default {
<gl-modal
modal-id="create-work-item-modal"
modal-class="create-work-item-modal"
body-class="!gl-pb-0"
:visible="isVisible"
scrollable
size="lg"
hide-footer
@hide="hideModal"
@ -261,6 +263,7 @@ export default {
<create-work-item
:description="description"
hide-form-title
sticky-form-submit
:is-group="isGroup"
:parent-id="parentId"
:show-project-selector="showProjectSelector"

View File

@ -848,6 +848,29 @@
}
}
.mr-widget-status-icon-neutral,
.mr-widget-status-icon-notice {
color: var(--gl-status-neutral-icon-color);
}
.mr-widget-status-icon-warning {
color: var(--gl-status-warning-icon-color);
}
.mr-widget-status-icon-failed,
.mr-widget-status-icon-error {
color: var(--gl-status-danger-icon-color);
}
.mr-widget-status-icon-success {
color: var(--gl-status-success-icon-color);
}
.mr-widget-status-icon-info,
.mr-widget-status-icon-scheduled {
color: var(--gl-status-info-icon-color);
}
.mr-widget-status-icon-level-1::before {
content: '';
position: absolute;

View File

@ -53,10 +53,15 @@ module ProtectedBranches
return unless (group = project_or_group).is_a?(Group)
group.all_projects.find_each do |project|
group.all_projects.each_batch do |projects_relation|
# First we remove the cache for each project in the group and then
# touch the projects_relation to update the projects' cache key.
with_redis do |redis|
redis.unlink redis_key(project)
projects_relation.each do |project|
redis.unlink redis_key(project)
end
end
projects_relation.touch_all
end
end

View File

@ -0,0 +1,35 @@
- title: "Pipeline job limits extended to the Commits API"
# The milestones for the deprecation announcement, and the removal.
removal_milestone: "18.0"
announcement_milestone: "17.7"
# Change breaking_change to false if needed.
breaking_change: true
window: 1 # Can be 1, 2, or 3 - The window when the breaking change will be deployed on GitLab.com
reporter: rutshah # The GitLab username of the person reporting the change
stage: verify
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/436361
# Use the impact calculator https://gitlab-com.gitlab.io/gl-infra/breaking-change-impact-calculator/?
impact: low # Can be one of: [critical, high, medium, low]
scope: project # Can be one or a combination of: [instance, group, project]
resolution_role: Owner # Can be one of: [Admin, Owner, Maintainer, Developer]
manual_task: true # Can be true or false. Use this to denote whether a resolution action must be performed manually (true), or if it can be automated by using the API or other automation (false).
body: | # (required) Don't change this line.
Starting in GitLab 18.0, the maximum [number of jobs in active pipelines](https://docs.gitlab.com/ee/administration/instance_limits.html#number-of-jobs-in-active-pipelines) will also apply when creating jobs using the [Commits API](https://docs.gitlab.com/ee/api/commits.html#set-the-pipeline-status-of-a-commit). Review your integration to ensure it stays within the configured job limits.
# ==============================
# OPTIONAL END-OF-SUPPORT FIELDS
# ==============================
#
# If an End of Support period applies:
# 1) Share this announcement in the `#spt_managers` Support channel in Slack
# 2) Mention `@gitlab-com/support` in this merge request.
#
# When support for this feature ends, in XX.YY milestone format.
end_of_support_milestone:
# Array of tiers the feature is currently available to,
# like [Free, Silver, Gold, Core, Premium, Ultimate]
tiers:
# Links to documentation and thumbnail image
documentation_url:
image_url:
# Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
video_url:

View File

@ -45,6 +45,8 @@ FactoryBot already reflects the change.
### Without GDK
Requires Git v2.26.0 or later.
1. Start a containerized GitLab instance
```shell
@ -120,6 +122,8 @@ Where `:file` is the file path. (This path reflects relative `.rb`, `.yml`, or `
WARNING:
While it is possible to use the Data Seeder with an Linux package installation, **use caution** if you do this when the instance is being used in a production setting.
Requires Git v2.26.0 or later.
1. Change the working directory to the GitLab installation:
```shell

View File

@ -550,6 +550,22 @@ For information about migrating from the CI/CD template to the component, see th
<div class="deprecation breaking-change" data-milestone="18.0">
### Pipeline job limits extended to the Commits API
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">17.7</span>
- Removal in GitLab <span class="milestone">18.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/436361).
</div>
Starting in GitLab 18.0, the maximum [number of jobs in active pipelines](https://docs.gitlab.com/ee/administration/instance_limits.html#number-of-jobs-in-active-pipelines) will also apply when creating jobs using the [Commits API](https://docs.gitlab.com/ee/api/commits.html#set-the-pipeline-status-of-a-commit). Review your integration to ensure it stays within the configured job limits.
</div>
<div class="deprecation breaking-change" data-milestone="18.0">
### Pipeline subscriptions
<div class="deprecation-notes">

View File

@ -65,6 +65,7 @@ module API
expires_in: 60.minutes,
cache_context: ->(branch) {
[
user_project.cache_key,
current_user&.cache_key,
merged_branch_names.include?(branch.name),
user_project.default_branch

View File

@ -1,5 +1,5 @@
variables:
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.104.0'
DAST_AUTO_DEPLOY_IMAGE_VERSION: 'v2.112.0'
.dast-auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${DAST_AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.104.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.112.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -1,5 +1,5 @@
variables:
AUTO_DEPLOY_IMAGE_VERSION: 'v2.104.0'
AUTO_DEPLOY_IMAGE_VERSION: 'v2.112.0'
.auto-deploy:
image: "${CI_TEMPLATE_REGISTRY_HOST}/gitlab-org/cluster-integration/auto-deploy-image:${AUTO_DEPLOY_IMAGE_VERSION}"

View File

@ -45,6 +45,7 @@ import {
secondsToDays,
secondsToMilliseconds,
totalDaysInMonth,
daysToSeconds,
} from '~/lib/utils/datetime/date_calculation_utility';
import { useFakeDate } from 'helpers/fake_date';
@ -946,3 +947,15 @@ describe('getStartOfWeek', () => {
},
);
});
describe('daysToSeconds', () => {
it('converts days to seconds correctly', () => {
expect(daysToSeconds(0)).toBe(0);
expect(daysToSeconds(0.1)).toBe(8640);
expect(daysToSeconds(0.5)).toBe(43200);
expect(daysToSeconds(1)).toBe(86400);
expect(daysToSeconds(2.5)).toBe(216000);
expect(daysToSeconds(3)).toBe(259200);
expect(daysToSeconds(5)).toBe(432000);
});
});

View File

@ -15,7 +15,6 @@ import {
storageTypeHelpPaths,
PROJECT_STORAGE_TYPES,
NAMESPACE_STORAGE_TYPES,
TOTAL_USAGE_DEFAULT_TEXT,
} from '~/usage_quotas/storage/constants';
import getCostFactoredProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/project/queries/cost_factored_project_storage.query.graphql';
import getProjectStorageStatistics from 'ee_else_ce/usage_quotas/storage/project/queries/project_storage.query.graphql';
@ -164,7 +163,7 @@ describe('ProjectStorageApp', () => {
});
it('shows default text for total usage', () => {
expect(findUsagePercentage().text()).toBe(TOTAL_USAGE_DEFAULT_TEXT);
expect(findUsagePercentage().text()).toBe('Not applicable.');
});
it('passes empty array to project details table', () => {

View File

@ -1,5 +1,5 @@
import { GlTableLite } from '@gitlab/ui';
import { mount, Wrapper } from '@vue/test-utils'; // eslint-disable-line no-unused-vars
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ProjectStorageDetail from '~/usage_quotas/storage/project/components/project_storage_detail.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';

View File

@ -1,6 +1,6 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlAlert, GlFormSelect } from '@gitlab/ui';
import { GlAlert, GlButton, GlFormSelect } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import namespaceWorkItemTypesQueryResponse from 'test_fixtures/graphql/work_items/namespace_work_item_types.query.graphql.json';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
@ -83,6 +83,7 @@ describe('Create work item component', () => {
const findRelatesToCheckbox = () => wrapper.find('[data-testid="relates-to-checkbox"]');
const findCreateWorkItemView = () => wrapper.find('[data-testid="create-work-item-view"]');
const findFormButtons = () => wrapper.find('[data-testid="form-buttons"]');
const findCreateButton = () => wrapper.find('[data-testid="create-button"]');
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]');
@ -539,4 +540,30 @@ describe('Create work item component', () => {
});
});
});
describe('form buttons', () => {
it('shows buttons on right and sticky when stickyFormSubmit', async () => {
await initialiseComponentAndSelectWorkItem({
props: { stickyFormSubmit: true },
});
expect(findFormButtons().classes('gl-sticky')).toBe(true);
expect(findFormButtons().classes('gl-justify-end')).toBe(true);
expect(findFormButtons().findAllComponents(GlButton).at(0).text()).toBe('Cancel');
expect(findFormButtons().findAllComponents(GlButton).at(1).text()).toBe('Create epic');
});
it('shows buttons on left and inside the grid when not stickyFormSubmit', async () => {
await initialiseComponentAndSelectWorkItem({
props: { stickyFormSubmit: false },
});
expect(findFormButtons().classes('gl-sticky')).toBe(false);
expect(findFormButtons().classes('gl-justify-end')).toBe(false);
expect(findFormButtons().findAllComponents(GlButton).at(0).text()).toBe('Create epic');
expect(findFormButtons().findAllComponents(GlButton).at(1).text()).toBe('Cancel');
});
});
});

View File

@ -212,7 +212,7 @@ RSpec.describe API::Branches, feature_category: :source_code_management do
it_behaves_like 'repository branches'
context 'caching' do
describe 'caching' do
it 'caches the query' do
get api(route), params: { per_page: 1 }
@ -243,24 +243,34 @@ RSpec.describe API::Branches, feature_category: :source_code_management do
end
end
context 'requests for new value if cache context changes' do
context 'with changes in default_branch' do
it 'requests for new value after 30 seconds' do
context 'when the default_branch changes' do
it 'requests for new value after 30 seconds' do
get api(route), params: { per_page: 1 }
default_branch = project.default_branch
another_branch = project.repository.branch_names.reject { |name| name == default_branch }.first
project.repository.change_head(another_branch)
travel_to 31.seconds.from_now do
expect(API::Entities::Branch).to receive(:represent)
get api(route), params: { per_page: 1 }
default_branch = project.default_branch
another_branch = project.repository.branch_names.reject { |name| name == default_branch }.first
project.repository.change_head(another_branch)
travel_to 31.seconds.from_now do
expect(API::Entities::Branch).to receive(:represent)
get api(route), params: { per_page: 1 }
end
end
end
end
context "when the project's protected branches change" do
it 'request for new value instantly' do
get api(route), params: { per_page: 1 }
ProtectedBranches::CreateService.new(project, user, { name: '*' }).execute(skip_authorization: true)
expect(API::Entities::Branch).to receive(:represent)
get api(route), params: { per_page: 1 }
end
end
end
end

View File

@ -101,17 +101,22 @@ RSpec.describe ProtectedBranches::CacheService, :clean_gitlab_redis_cache, featu
describe '#refresh' do
let(:project_service) { described_class.new(project, user) }
it 'clears cached values' do
it 'clears cached values', time_travel_to: 1.minute.from_now do
expect(service.fetch('main') { true }).to eq(true)
expect(service.fetch('not-found') { false }).to eq(false)
if entity.is_a?(Group)
expect(project_service.fetch('main') { true }).to eq(true)
expect(project_service.fetch('not-found') { false }).to eq(false)
# When refreshing a group we also touch each project within the
# group.
expect { service.refresh }.to change { project.reload.updated_at }.to(Time.current)
else
# We already touch the project when updating a project's protected
# branches so refresh shouldn't affect the project
expect { service.refresh }.not_to change { project.reload.updated_at }
end
service.refresh
# Recreates cache
expect(service.fetch('main') { false }).to eq(false)
expect(service.fetch('not-found') { true }).to eq(true)

View File

@ -7,6 +7,11 @@ RSpec.shared_examples 'protected ref' do
subject(:described_instance) { build(factory, project: project) }
describe 'Associations' do
# One purpose of touching the project is cache keys. We have endpoints that
# use the project in the cache key. This calls the project.cache_key method
# which uses the timestamp as part of the key. If we remove `touch: true`
# we will need to update the cache keys to use a different mechanism to
# expire the cache.
it { is_expected.to belong_to(:project).touch(true) }
end