Commit a7a7f8b1 authored by Kamil Trzciński's avatar Kamil Trzciński

Merge branch '41249-clearing-the-cache' into 'master'

Resolve "Clearing the cache"

Closes #41249

See merge request gitlab-org/gitlab-ce!16067
parents 6d972724 ff5124ed
...@@ -17,6 +17,11 @@ export default { ...@@ -17,6 +17,11 @@ export default {
required: true, required: true,
}, },
resetCachePath: {
type: String,
required: true,
},
ciLintPath: { ciLintPath: {
type: String, type: String,
required: true, required: true,
...@@ -45,6 +50,14 @@ export default { ...@@ -45,6 +50,14 @@ export default {
Get started with Pipelines Get started with Pipelines
</a> </a>
<a
data-method="post"
rel="nofollow"
:href="resetCachePath"
class="btn btn-default">
Clear runner caches
</a>
<a <a
:href="ciLintPath" :href="ciLintPath"
class="btn btn-default"> class="btn btn-default">
......
...@@ -50,6 +50,7 @@ ...@@ -50,6 +50,7 @@
canCreatePipeline: pipelinesData.canCreatePipeline, canCreatePipeline: pipelinesData.canCreatePipeline,
hasCi: pipelinesData.hasCi, hasCi: pipelinesData.hasCi,
ciLintPath: pipelinesData.ciLintPath, ciLintPath: pipelinesData.ciLintPath,
resetCachePath: pipelinesData.resetCachePath,
state: this.store.state, state: this.store.state,
scope: getParameterByName('scope') || 'all', scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1', page: getParameterByName('page') || '1',
...@@ -220,6 +221,7 @@ ...@@ -220,6 +221,7 @@
:new-pipeline-path="newPipelinePath" :new-pipeline-path="newPipelinePath"
:has-ci-enabled="hasCiEnabled" :has-ci-enabled="hasCiEnabled"
:help-page-path="helpPagePath" :help-page-path="helpPagePath"
:resetCachePath="resetCachePath"
:ci-lint-path="ciLintPath" :ci-lint-path="ciLintPath"
:can-create-pipeline="canCreatePipelineParsed " :can-create-pipeline="canCreatePipelineParsed "
/> />
......
...@@ -11,6 +11,16 @@ module Projects ...@@ -11,6 +11,16 @@ module Projects
define_auto_devops_variables define_auto_devops_variables
end end
def reset_cache
if ResetProjectCacheService.new(@project, current_user).execute
flash[:notice] = _("Project cache successfully reset.")
else
flash[:error] = _("Unable to reset project cache.")
end
redirect_to project_pipelines_path(@project)
end
private private
def define_runners_variables def define_runners_variables
......
...@@ -461,7 +461,14 @@ module Ci ...@@ -461,7 +461,14 @@ module Ci
end end
def cache def cache
[options[:cache]] cache = options[:cache]
if cache && project.jobs_cache_index
cache = cache.merge(
key: "#{cache[:key]}:#{project.jobs_cache_index}")
end
[cache]
end end
def credentials def credentials
......
class ResetProjectCacheService < BaseService
def execute
@project.increment!(:jobs_cache_index)
end
end
...@@ -10,7 +10,8 @@ ...@@ -10,7 +10,8 @@
"new-pipeline-path" => new_project_pipeline_path(@project), "new-pipeline-path" => new_project_pipeline_path(@project),
"can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s,
"has-ci" => @repository.gitlab_ci_yml, "has-ci" => @repository.gitlab_ci_yml,
"ci-lint-path" => ci_lint_path } } "ci-lint-path" => ci_lint_path,
"reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } }
= page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('pipelines') = page_specific_javascript_bundle_tag('pipelines')
---
title: Implement project jobs cache reset
merge_request: 16067
author:
type: added
...@@ -408,7 +408,9 @@ constraints(ProjectUrlConstrainer.new) do ...@@ -408,7 +408,9 @@ constraints(ProjectUrlConstrainer.new) do
end end
namespace :settings do namespace :settings do
get :members, to: redirect("%{namespace_id}/%{project_id}/project_members") get :members, to: redirect("%{namespace_id}/%{project_id}/project_members")
resource :ci_cd, only: [:show], controller: 'ci_cd' resource :ci_cd, only: [:show], controller: 'ci_cd' do
post :reset_cache
end
resource :integrations, only: [:show] resource :integrations, only: [:show]
resource :repository, only: [:show], controller: :repository resource :repository, only: [:show], controller: :repository
end end
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddJobsCacheIndexToProject < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :projects, :jobs_cache_index, :integer
end
end
...@@ -1451,6 +1451,7 @@ ActiveRecord::Schema.define(version: 20171230123729) do ...@@ -1451,6 +1451,7 @@ ActiveRecord::Schema.define(version: 20171230123729) do
t.boolean "repository_read_only" t.boolean "repository_read_only"
t.boolean "merge_requests_ff_only_enabled", default: false t.boolean "merge_requests_ff_only_enabled", default: false
t.boolean "merge_requests_rebase_enabled", default: false, null: false t.boolean "merge_requests_rebase_enabled", default: false, null: false
t.integer "jobs_cache_index"
end end
add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree
......
...@@ -17,4 +17,51 @@ describe Projects::Settings::CiCdController do ...@@ -17,4 +17,51 @@ describe Projects::Settings::CiCdController do
expect(response).to render_template(:show) expect(response).to render_template(:show)
end end
end end
describe '#reset_cache' do
before do
sign_in(user)
project.add_master(user)
allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true)
end
subject { post :reset_cache, namespace_id: project.namespace, project_id: project }
it 'calls reset project cache service' do
expect(ResetProjectCacheService).to receive_message_chain(:new, :execute)
subject
end
it 'redirects to project pipelines path' do
subject
expect(response).to have_gitlab_http_status(:redirect)
expect(response).to redirect_to(project_pipelines_path(project))
end
context 'when service returns successfully' do
it 'sets the flash notice variable' do
subject
expect(controller).to set_flash[:notice]
expect(controller).not_to set_flash[:error]
end
end
context 'when service does not return successfully' do
before do
allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false)
end
it 'sets the flash error variable' do
subject
expect(controller).not_to set_flash[:notice]
expect(controller).to set_flash[:error]
end
end
end
end end
...@@ -545,6 +545,40 @@ describe 'Pipelines', :js do ...@@ -545,6 +545,40 @@ describe 'Pipelines', :js do
end end
end end
end end
describe 'Reset runner caches' do
let(:project) { create(:project, :repository) }
before do
create(:ci_empty_pipeline, status: 'success', project: project, sha: project.commit.id, ref: 'master')
project.add_master(user)
visit project_pipelines_path(project)
end
it 'has a clear caches button' do
expect(page).to have_link 'Clear runner caches'
end
describe 'user clicks the button' do
context 'when project already has jobs_cache_index' do
before do
project.update_attributes(jobs_cache_index: 1)
end
it 'increments jobs_cache_index' do
click_link 'Clear runner caches'
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
context 'when project does not have jobs_cache_index' do
it 'sets jobs_cache_index to 1' do
click_link 'Clear runner caches'
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
end
end
end end
context 'when user is not logged in' do context 'when user is not logged in' do
......
...@@ -7,4 +7,6 @@ ...@@ -7,4 +7,6 @@
"new-pipeline-path" => 'foo', "new-pipeline-path" => 'foo',
"can-create-pipeline" => 'true', "can-create-pipeline" => 'true',
"has-ci" => 'foo', "has-ci" => 'foo',
"ci-lint-path" => 'foo' } } "ci-lint-path" => 'foo',
"reset-cache-path" => 'foo' } }
...@@ -14,6 +14,7 @@ describe('Pipelines Nav Controls', () => { ...@@ -14,6 +14,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true, hasCiEnabled: true,
helpPagePath: 'foo', helpPagePath: 'foo',
ciLintPath: 'foo', ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true, canCreatePipeline: true,
}; };
...@@ -31,6 +32,7 @@ describe('Pipelines Nav Controls', () => { ...@@ -31,6 +32,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true, hasCiEnabled: true,
helpPagePath: 'foo', helpPagePath: 'foo',
ciLintPath: 'foo', ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: false, canCreatePipeline: false,
}; };
...@@ -41,12 +43,31 @@ describe('Pipelines Nav Controls', () => { ...@@ -41,12 +43,31 @@ describe('Pipelines Nav Controls', () => {
expect(component.$el.querySelector('.btn-create')).toEqual(null); expect(component.$el.querySelector('.btn-create')).toEqual(null);
}); });
it('should render link for resetting runner caches', () => {
const mockData = {
newPipelinePath: 'foo',
hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: false,
};
const component = new NavControlsComponent({
propsData: mockData,
}).$mount();
expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches');
expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath);
});
it('should render link for CI lint', () => { it('should render link for CI lint', () => {
const mockData = { const mockData = {
newPipelinePath: 'foo', newPipelinePath: 'foo',
hasCiEnabled: true, hasCiEnabled: true,
helpPagePath: 'foo', helpPagePath: 'foo',
ciLintPath: 'foo', ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true, canCreatePipeline: true,
}; };
...@@ -54,8 +75,8 @@ describe('Pipelines Nav Controls', () => { ...@@ -54,8 +75,8 @@ describe('Pipelines Nav Controls', () => {
propsData: mockData, propsData: mockData,
}).$mount(); }).$mount();
expect(component.$el.querySelector('.btn-default').textContent).toContain('CI Lint'); expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint');
expect(component.$el.querySelector('.btn-default').getAttribute('href')).toEqual(mockData.ciLintPath); expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath);
}); });
it('should render link to help page when CI is not enabled', () => { it('should render link to help page when CI is not enabled', () => {
...@@ -64,6 +85,7 @@ describe('Pipelines Nav Controls', () => { ...@@ -64,6 +85,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: false, hasCiEnabled: false,
helpPagePath: 'foo', helpPagePath: 'foo',
ciLintPath: 'foo', ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true, canCreatePipeline: true,
}; };
...@@ -81,6 +103,7 @@ describe('Pipelines Nav Controls', () => { ...@@ -81,6 +103,7 @@ describe('Pipelines Nav Controls', () => {
hasCiEnabled: true, hasCiEnabled: true,
helpPagePath: 'foo', helpPagePath: 'foo',
ciLintPath: 'foo', ciLintPath: 'foo',
resetCachePath: 'foo',
canCreatePipeline: true, canCreatePipeline: true,
}; };
......
...@@ -459,6 +459,7 @@ Project: ...@@ -459,6 +459,7 @@ Project:
- delete_error - delete_error
- merge_requests_ff_only_enabled - merge_requests_ff_only_enabled
- merge_requests_rebase_enabled - merge_requests_rebase_enabled
- jobs_cache_index
Author: Author:
- name - name
ProjectFeature: ProjectFeature:
......
...@@ -255,6 +255,42 @@ describe Ci::Build do ...@@ -255,6 +255,42 @@ describe Ci::Build do
end end
end end
describe '#cache' do
let(:options) { { cache: { key: "key", paths: ["public"], policy: "pull-push" } } }
subject { build.cache }
context 'when build has cache' do
before do
allow(build).to receive(:options).and_return(options)
end
context 'when project has jobs_cache_index' do
before do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
end
it { is_expected.to be_an(Array).and all(include(key: "key:1")) }
end
context 'when project does not have jobs_cache_index' do
before do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
end
it { is_expected.to eq([options[:cache]]) }
end
end
context 'when build does not have cache' do
before do
allow(build).to receive(:options).and_return({})
end
it { is_expected.to eq([nil]) }
end
end
describe '#depends_on_builds' do describe '#depends_on_builds' do
let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
......
require 'spec_helper'
describe ResetProjectCacheService do
let(:project) { create(:project) }
let(:user) { create(:user) }
subject { described_class.new(project, user).execute }
context 'when project cache_index is nil' do
before do
project.jobs_cache_index = nil
end
it 'sets project cache_index to one' do
expect { subject }.to change { project.reload.jobs_cache_index }.from(nil).to(1)
end
end
context 'when project cache_index is a numeric value' do
before do
project.update_attributes(jobs_cache_index: 1)
end
it 'increments project cache index' do
expect { subject }.to change { project.reload.jobs_cache_index }.by(1)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment