Commit e1e45fc1 authored by GitLab Bot's avatar GitLab Bot

Merge remote-tracking branch 'upstream/master' into ce-to-ee-2018-03-08

parents 38c08da7 0f26f968
...@@ -53,7 +53,7 @@ Below we describe the contributing process to GitLab for two reasons: ...@@ -53,7 +53,7 @@ Below we describe the contributing process to GitLab for two reasons:
Several people from the [GitLab team][team] are helping community members to get Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][done]. their contributions accepted by meeting our [Definition of done][done].
What you can expect from them is described at https://about.gitlab.com/jobs/merge-request-coach/. What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/.
## Assigning issues ## Assigning issues
...@@ -202,6 +202,9 @@ you can ask for an exception to be made. ...@@ -202,6 +202,9 @@ you can ask for an exception to be made.
Go to [Release tasks issue tracker](https://gitlab.com/gitlab-org/release/tasks/issues/new) and create an issue Go to [Release tasks issue tracker](https://gitlab.com/gitlab-org/release/tasks/issues/new) and create an issue
using the `Exception-request` issue template. using the `Exception-request` issue template.
**Do not** set the relevant `Pick into X.Y` label (see above) before request an
exception; this should be done after the exception is approved.
You can find who is who on the [team page](https://about.gitlab.com/team/). You can find who is who on the [team page](https://about.gitlab.com/team/).
Whether an exception is made is determined by weighing the benefit and urgency of the change Whether an exception is made is determined by weighing the benefit and urgency of the change
......
<script> <script>
import LoadingButton from '../../vue_shared/components/loading_button.vue';
export default { export default {
name: 'PipelineNavControls', name: 'PipelineNavControls',
components: {
LoadingButton,
},
props: { props: {
newPipelinePath: { newPipelinePath: {
type: String, type: String,
...@@ -19,6 +24,17 @@ ...@@ -19,6 +24,17 @@
required: false, required: false,
default: null, default: null,
}, },
isResetCacheButtonLoading: {
type: Boolean,
required: false,
default: false,
},
},
methods: {
onClickResetCache() {
this.$emit('resetRunnersCache', this.resetCachePath);
},
}, },
}; };
</script> </script>
...@@ -32,14 +48,13 @@ ...@@ -32,14 +48,13 @@
{{ s__('Pipelines|Run Pipeline') }} {{ s__('Pipelines|Run Pipeline') }}
</a> </a>
<a <loading-button
v-if="resetCachePath" v-if="resetCachePath"
data-method="post" @click="onClickResetCache"
:href="resetCachePath" :loading="isResetCacheButtonLoading"
class="btn btn-default js-clear-cache" class="btn btn-default js-clear-cache"
> :label="s__('Pipelines|Clear Runner Caches')"
{{ s__('Pipelines|Clear Runner Caches') }} />
</a>
<a <a
v-if="ciLintPath" v-if="ciLintPath"
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import { __, sprintf, s__ } from '../../locale'; import { __, sprintf, s__ } from '../../locale';
import createFlash from '../../flash';
import PipelinesService from '../services/pipelines_service'; import PipelinesService from '../services/pipelines_service';
import pipelinesMixin from '../mixins/pipelines'; import pipelinesMixin from '../mixins/pipelines';
import TablePagination from '../../vue_shared/components/table_pagination.vue'; import TablePagination from '../../vue_shared/components/table_pagination.vue';
...@@ -92,6 +93,7 @@ ...@@ -92,6 +93,7 @@
scope: getParameterByName('scope') || 'all', scope: getParameterByName('scope') || 'all',
page: getParameterByName('page') || '1', page: getParameterByName('page') || '1',
requestData: {}, requestData: {},
isResetCacheButtonLoading: false,
}; };
}, },
stateMap: { stateMap: {
...@@ -265,6 +267,23 @@ ...@@ -265,6 +267,23 @@
this.poll.restart({ data: this.requestData }); this.poll.restart({ data: this.requestData });
}); });
}, },
handleResetRunnersCache(endpoint) {
this.isResetCacheButtonLoading = true;
this.service.postAction(endpoint)
.then(() => {
this.isResetCacheButtonLoading = false;
createFlash(
s__('Pipelines|Project cache successfully reset.'),
'notice',
);
})
.catch(() => {
this.isResetCacheButtonLoading = false;
createFlash(s__('Pipelines|Something went wrong while cleaning runners cache.'));
});
},
}, },
}; };
</script> </script>
...@@ -301,6 +320,8 @@ ...@@ -301,6 +320,8 @@
:new-pipeline-path="newPipelinePath" :new-pipeline-path="newPipelinePath"
:reset-cache-path="resetCachePath" :reset-cache-path="resetCachePath"
:ci-lint-path="ciLintPath" :ci-lint-path="ciLintPath"
@resetRunnersCache="handleResetRunnersCache"
:is-reset-cache-button-loading="isResetCacheButtonLoading"
/> />
</div> </div>
......
...@@ -51,12 +51,10 @@ export default { ...@@ -51,12 +51,10 @@ export default {
} }
}); });
eventHub.$on('refreshPipelines', this.fetchPipelines);
eventHub.$on('postAction', this.postAction); eventHub.$on('postAction', this.postAction);
}, },
beforeDestroy() { beforeDestroy() {
eventHub.$off('refreshPipelines'); eventHub.$off('postAction', this.postAction);
eventHub.$on('postAction', this.postAction);
}, },
destroyed() { destroyed() {
this.poll.stop(); this.poll.stop();
...@@ -92,7 +90,7 @@ export default { ...@@ -92,7 +90,7 @@ export default {
}, },
postAction(endpoint) { postAction(endpoint) {
this.service.postAction(endpoint) this.service.postAction(endpoint)
.then(() => eventHub.$emit('refreshPipelines')) .then(() => this.fetchPipelines())
.catch(() => Flash(__('An error occurred while making the request.'))); .catch(() => Flash(__('An error occurred while making the request.')));
}, },
}, },
......
...@@ -13,12 +13,14 @@ module Projects ...@@ -13,12 +13,14 @@ module Projects
def reset_cache def reset_cache
if ResetProjectCacheService.new(@project, current_user).execute if ResetProjectCacheService.new(@project, current_user).execute
flash[:notice] = _("Project cache successfully reset.") respond_to do |format|
format.json { head :ok }
end
else else
flash[:error] = _("Unable to reset project cache.") respond_to do |format|
format.json { head :bad_request }
end
end end
redirect_to project_pipelines_path(@project)
end end
private private
......
...@@ -42,7 +42,7 @@ class Repository ...@@ -42,7 +42,7 @@ class Repository
CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide CACHED_METHODS = %i(size commit_count rendered_readme contribution_guide
changelog license_blob license_key gitignore koding_yml changelog license_blob license_key gitignore koding_yml
gitlab_ci_yml branch_names tag_names branch_count gitlab_ci_yml branch_names tag_names branch_count
tag_count avatar exists? empty? root_ref has_visible_content? tag_count avatar exists? root_ref has_visible_content?
issue_template_names merge_request_template_names).freeze issue_template_names merge_request_template_names).freeze
# Methods that use cache_method but only memoize the value # Methods that use cache_method but only memoize the value
...@@ -367,7 +367,7 @@ class Repository ...@@ -367,7 +367,7 @@ class Repository
def expire_emptiness_caches def expire_emptiness_caches
return unless empty? return unless empty?
expire_method_caches(%i(empty? has_visible_content?)) expire_method_caches(%i(has_visible_content?))
end end
def lookup_cache def lookup_cache
...@@ -513,12 +513,14 @@ class Repository ...@@ -513,12 +513,14 @@ class Repository
end end
cache_method :exists? cache_method :exists?
# We don't need to cache the output of this method because both exists? and
# has_visible_content? are already memoized and cached. There's no guarantee
# that the values are expired and loaded atomically.
def empty? def empty?
return true unless exists? return true unless exists?
!has_visible_content? !has_visible_content?
end end
cache_method :empty?
# The size of this repository in megabytes. # The size of this repository in megabytes.
def size def size
......
---
title: Hook data for pipelines includes detailed_status
merge_request: 17607
author:
type: changed
---
title: Avoid showing unnecessary Trigger checkboxes for project Integrations with
only one event
merge_request: 17607
author:
type: changed
---
title: Fixed group deletion linked to Mattermost
merge_request: 16209
author: Julien Millau
type: fixed
---
title: Remove double caching of Repository#empty?
merge_request:
author:
type: fixed
...@@ -83,6 +83,12 @@ module Mattermost ...@@ -83,6 +83,12 @@ module Mattermost
end end
end end
def delete(path, options = {})
handle_exceptions do
self.class.delete(path, options.merge(headers: @headers))
end
end
private private
def create def create
......
...@@ -16,10 +16,9 @@ module Mattermost ...@@ -16,10 +16,9 @@ module Mattermost
end end
# The deletion is done async, so the response is fast. # The deletion is done async, so the response is fast.
# On the mattermost side, this triggers an soft deletion first, after which # On the mattermost side, this triggers an soft deletion
# the actuall data is removed
def destroy(team_id:) def destroy(team_id:)
session_delete("/api/v4/teams/#{team_id}?permanent=true") session_delete("/api/v4/teams/#{team_id}")
end end
end end
end end
...@@ -27,7 +27,7 @@ describe Projects::Settings::CiCdController do ...@@ -27,7 +27,7 @@ describe Projects::Settings::CiCdController do
allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true) allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true)
end end
subject { post :reset_cache, namespace_id: project.namespace, project_id: project } subject { post :reset_cache, namespace_id: project.namespace, project_id: project, format: :json }
it 'calls reset project cache service' do it 'calls reset project cache service' do
expect(ResetProjectCacheService).to receive_message_chain(:new, :execute) expect(ResetProjectCacheService).to receive_message_chain(:new, :execute)
...@@ -35,19 +35,11 @@ describe Projects::Settings::CiCdController do ...@@ -35,19 +35,11 @@ describe Projects::Settings::CiCdController do
subject subject
end 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 context 'when service returns successfully' do
it 'sets the flash notice variable' do it 'returns a success header' do
subject subject
expect(controller).to set_flash[:notice] expect(response).to have_gitlab_http_status(:ok)
expect(controller).not_to set_flash[:error]
end end
end end
...@@ -56,11 +48,10 @@ describe Projects::Settings::CiCdController do ...@@ -56,11 +48,10 @@ describe Projects::Settings::CiCdController do
allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false) allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false)
end end
it 'sets the flash error variable' do it 'returns an error header' do
subject subject
expect(controller).not_to set_flash[:notice] expect(response).to have_gitlab_http_status(:bad_request)
expect(controller).to set_flash[:error]
end end
end end
end end
......
...@@ -557,7 +557,7 @@ describe 'Pipelines', :js do ...@@ -557,7 +557,7 @@ describe 'Pipelines', :js do
end end
it 'has a clear caches button' do it 'has a clear caches button' do
expect(page).to have_link 'Clear Runner Caches' expect(page).to have_button 'Clear Runner Caches'
end end
describe 'user clicks the button' do describe 'user clicks the button' do
...@@ -567,14 +567,16 @@ describe 'Pipelines', :js do ...@@ -567,14 +567,16 @@ describe 'Pipelines', :js do
end end
it 'increments jobs_cache_index' do it 'increments jobs_cache_index' do
click_link 'Clear Runner Caches' click_button 'Clear Runner Caches'
wait_for_requests
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end end
end end
context 'when project does not have jobs_cache_index' do context 'when project does not have jobs_cache_index' do
it 'sets jobs_cache_index to 1' do it 'sets jobs_cache_index to 1' do
click_link 'Clear Runner Caches' click_button 'Clear Runner Caches'
wait_for_requests
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end end
end end
......
require 'spec_helper'
describe 'Disable individual triggers' do
let(:project) { create(:project) }
let(:user) { project.owner }
let(:checkbox_selector) { 'input[type=checkbox][id$=_events]' }
before do
sign_in(user)
visit(project_settings_integrations_path(project))
click_link(service_name)
end
context 'service has multiple supported events' do
let(:service_name) { 'HipChat' }
it 'shows trigger checkboxes' do
event_count = HipchatService.supported_events.count
expect(page).to have_content "Trigger"
expect(page).to have_css(checkbox_selector, count: event_count)
end
end
context 'services only has one supported event' do
let(:service_name) { 'Asana' }
it "doesn't show unnecessary Trigger checkboxes" do
expect(page).not_to have_content "Trigger"
expect(page).not_to have_css(checkbox_selector)
end
end
end
...@@ -39,30 +39,41 @@ describe('Pipelines Nav Controls', () => { ...@@ -39,30 +39,41 @@ describe('Pipelines Nav Controls', () => {
expect(component.$el.querySelector('.js-run-pipeline')).toEqual(null); expect(component.$el.querySelector('.js-run-pipeline')).toEqual(null);
}); });
it('should render link for resetting runner caches', () => { it('should render link for CI lint', () => {
const mockData = { const mockData = {
newPipelinePath: 'foo', newPipelinePath: 'foo',
helpPagePath: 'foo',
ciLintPath: 'foo', ciLintPath: 'foo',
resetCachePath: 'foo', resetCachePath: 'foo',
}; };
component = mountComponent(NavControlsComponent, mockData); component = mountComponent(NavControlsComponent, mockData);
expect(component.$el.querySelector('.js-clear-cache').textContent.trim()).toContain('Clear Runner Caches'); expect(component.$el.querySelector('.js-ci-lint').textContent.trim()).toContain('CI Lint');
expect(component.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(mockData.resetCachePath); expect(component.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(mockData.ciLintPath);
}); });
it('should render link for CI lint', () => { describe('Reset Runners Cache', () => {
beforeEach(() => {
const mockData = { const mockData = {
newPipelinePath: 'foo', newPipelinePath: 'foo',
helpPagePath: 'foo',
ciLintPath: 'foo', ciLintPath: 'foo',
resetCachePath: 'foo', resetCachePath: 'foo',
}; };
component = mountComponent(NavControlsComponent, mockData); component = mountComponent(NavControlsComponent, mockData);
});
expect(component.$el.querySelector('.js-ci-lint').textContent.trim()).toContain('CI Lint'); it('should render button for resetting runner caches', () => {
expect(component.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(mockData.ciLintPath); expect(component.$el.querySelector('.js-clear-cache').textContent.trim()).toContain('Clear Runner Caches');
});
it('should emit postAction event when reset runner cache button is clicked', () => {
spyOn(component, '$emit');
component.$el.querySelector('.js-clear-cache').click();
expect(component.$emit).toHaveBeenCalledWith('resetRunnersCache', 'foo');
});
}); });
}); });
...@@ -95,16 +95,16 @@ describe('Pipelines', () => { ...@@ -95,16 +95,16 @@ describe('Pipelines', () => {
expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All'); expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
}); });
it('renders Run Pipeline button', () => { it('renders Run Pipeline link', () => {
expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath); expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
}); });
it('renders CI Lint button', () => { it('renders CI Lint link', () => {
expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath); expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
}); });
it('renders Clear Runner Cache button', () => { it('renders Clear Runner Cache button', () => {
expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath); expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
}); });
it('renders pipelines table', () => { it('renders pipelines table', () => {
...@@ -139,16 +139,16 @@ describe('Pipelines', () => { ...@@ -139,16 +139,16 @@ describe('Pipelines', () => {
expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All'); expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
}); });
it('renders Run Pipeline button', () => { it('renders Run Pipeline link', () => {
expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath); expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
}); });
it('renders CI Lint button', () => { it('renders CI Lint link', () => {
expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath); expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
}); });
it('renders Clear Runner Cache button', () => { it('renders Clear Runner Cache button', () => {
expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath); expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
}); });
it('renders tab empty state', () => { it('renders tab empty state', () => {
...@@ -218,7 +218,7 @@ describe('Pipelines', () => { ...@@ -218,7 +218,7 @@ describe('Pipelines', () => {
it('renders buttons', () => { it('renders buttons', () => {
expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath); expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath); expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath); expect(vm.$el.querySelector('.js-clear-cache').textContent.trim()).toEqual('Clear Runner Caches');
}); });
it('renders error state', () => { it('renders error state', () => {
......
...@@ -64,4 +64,108 @@ describe Mattermost::Team do ...@@ -64,4 +64,108 @@ describe Mattermost::Team do
end end
end end
end end
describe '#create' do
subject { described_class.new(nil).create(name: "devteam", display_name: "Dev Team", type: "O") }
context 'for a new team' do
let(:response) do
{
"id" => "cuojfcetjty7tb4pxe47pwpndo",
"create_at" => 1517688728701,
"update_at" => 1517688728701,
"delete_at" => 0,
"display_name" => "Dev Team",
"name" => "devteam",
"description" => "",
"email" => "admin@example.com",
"type" => "O",
"company_name" => "",
"allowed_domains" => "",
"invite_id" => "7mp9d3ayaj833ymmkfnid8js6w",
"allow_open_invite" => false
}
end
before do
stub_request(:post, "http://mattermost.example.com/api/v3/teams/create")
.to_return(
status: 200,
body: response.to_json,
headers: { 'Content-Type' => 'application/json' }
)
end
it 'returns the new team' do
is_expected.to eq(response)
end
end
context 'for existing team' do
before do
stub_request(:post, 'http://mattermost.example.com/api/v3/teams/create')
.to_return(
status: 400,
headers: { 'Content-Type' => 'application/json' },
body: {
id: "store.sql_team.save.domain_exists.app_error",
message: "A team with that name already exists",
detailed_error: "",
request_id: "1hsb5bxs97r8bdggayy7n9gxaw",
status_code: 400
}.to_json
)
end
it 'raises an error with message' do
expect { subject }.to raise_error(Mattermost::Error, 'A team with that name already exists')
end
end
end
describe '#delete' do
subject { described_class.new(nil).destroy(team_id: "cuojfcetjty7tb4pxe47pwpndo") }
context 'for an existing team' do
let(:response) do
{
"status" => "OK"
}
end
before do
stub_request(:delete, "http://mattermost.example.com/api/v4/teams/cuojfcetjty7tb4pxe47pwpndo")
.to_return(
status: 200,
body: response.to_json,
headers: { 'Content-Type' => 'application/json' }
)
end
it 'returns team status' do
is_expected.to eq(response)
end
end
context 'for an unknown team' do
before do
stub_request(:delete, "http://mattermost.example.com/api/v4/teams/cuojfcetjty7tb4pxe47pwpndo")
.to_return(
status: 404,
body: {
id: "store.sql_team.get.find.app_error",
message: "We couldn't find the existing team",
detailed_error: "",
request_id: "my114ab5nbnui8c9pes4kz8mza",
status_code: 404
}.to_json,
headers: { 'Content-Type' => 'application/json' }
)
end
it 'raises an error with message' do
expect { subject }.to raise_error(Mattermost::Error, "We couldn't find the existing team")
end
end
end
end end
...@@ -1479,7 +1479,6 @@ describe Repository do ...@@ -1479,7 +1479,6 @@ describe Repository do
it 'expires the caches for an empty repository' do it 'expires the caches for an empty repository' do
allow(repository).to receive(:empty?).and_return(true) allow(repository).to receive(:empty?).and_return(true)
expect(cache).to receive(:expire).with(:empty?)
expect(cache).to receive(:expire).with(:has_visible_content?) expect(cache).to receive(:expire).with(:has_visible_content?)
repository.expire_emptiness_caches repository.expire_emptiness_caches
...@@ -1488,7 +1487,6 @@ describe Repository do ...@@ -1488,7 +1487,6 @@ describe Repository do
it 'does not expire the cache for a non-empty repository' do it 'does not expire the cache for a non-empty repository' do
allow(repository).to receive(:empty?).and_return(false) allow(repository).to receive(:empty?).and_return(false)
expect(cache).not_to receive(:expire).with(:empty?)
expect(cache).not_to receive(:expire).with(:has_visible_content?) expect(cache).not_to receive(:expire).with(:has_visible_content?)
repository.expire_emptiness_caches repository.expire_emptiness_caches
......
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