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

Merge branch 'tc-fix-unplayable-build-action-404' into 'master'

Disable pipeline & environment actions that are not playable

Closes #25385 and #24601

See merge request !10052
parents 08393eca 474236e8
...@@ -45,11 +45,20 @@ export default { ...@@ -45,11 +45,20 @@ export default {
new Flash('An error occured while making the request.'); new Flash('An error occured while making the request.');
}); });
}, },
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
}
return !action.playable;
},
}, },
template: ` template: `
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<button <button
type="button"
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip" class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container has-tooltip"
data-container="body" data-container="body"
data-toggle="dropdown" data-toggle="dropdown"
...@@ -58,15 +67,23 @@ export default { ...@@ -58,15 +67,23 @@ export default {
:disabled="isLoading"> :disabled="isLoading">
<span> <span>
<span v-html="playIconSvg"></span> <span v-html="playIconSvg"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i> <i
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i> class="fa fa-caret-down"
aria-hidden="true"/>
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true"/>
</span> </span>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li v-for="action in actions">
<button <button
type="button"
class="js-manual-action-link no-btn btn"
@click="onClickAction(action.play_path)" @click="onClickAction(action.play_path)"
class="js-manual-action-link no-btn"> :class="{ 'disabled': isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
${playIconSvg} ${playIconSvg}
<span> <span>
{{action.name}} {{action.name}}
......
...@@ -142,6 +142,7 @@ export default { ...@@ -142,6 +142,7 @@ export default {
const parsedAction = { const parsedAction = {
name: gl.text.humanize(action.name), name: gl.text.humanize(action.name),
play_path: action.play_path, play_path: action.play_path,
playable: action.playable,
}; };
return parsedAction; return parsedAction;
}); });
......
...@@ -38,6 +38,14 @@ export default { ...@@ -38,6 +38,14 @@ export default {
new Flash('An error occured while making the request.'); new Flash('An error occured while making the request.');
}); });
}, },
isActionDisabled(action) {
if (action.playable === undefined) {
return false;
}
return !action.playable;
},
}, },
template: ` template: `
...@@ -51,16 +59,23 @@ export default { ...@@ -51,16 +59,23 @@ export default {
aria-label="Manual job" aria-label="Manual job"
:disabled="isLoading"> :disabled="isLoading">
${playIconSvg} ${playIconSvg}
<i class="fa fa-caret-down" aria-hidden="true"></i> <i
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i> class="fa fa-caret-down"
aria-hidden="true" />
<i
v-if="isLoading"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
</button> </button>
<ul class="dropdown-menu dropdown-menu-align-right"> <ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions"> <li v-for="action in actions">
<button <button
type="button" type="button"
class="js-pipeline-action-link no-btn" class="js-pipeline-action-link no-btn btn"
@click="onClickAction(action.path)"> @click="onClickAction(action.path)"
:class="{ 'disabled': isActionDisabled(action) }"
:disabled="isActionDisabled(action)">
${playIconSvg} ${playIconSvg}
<span>{{action.name}}</span> <span>{{action.name}}</span>
</button> </button>
......
...@@ -11,4 +11,6 @@ class BuildActionEntity < Grape::Entity ...@@ -11,4 +11,6 @@ class BuildActionEntity < Grape::Entity
build.project, build.project,
build) build)
end end
expose :playable?, as: :playable
end end
...@@ -16,6 +16,7 @@ class BuildEntity < Grape::Entity ...@@ -16,6 +16,7 @@ class BuildEntity < Grape::Entity
path_to(:play_namespace_project_build, build) path_to(:play_namespace_project_build, build)
end end
expose :playable?, as: :playable
expose :created_at expose :created_at
expose :updated_at expose :updated_at
expose :detailed_status, as: :status, with: StatusEntity expose :detailed_status, as: :status, with: StatusEntity
......
---
title: Disable pipeline and environment actions that are not playable
merge_request: 10052
author:
...@@ -192,5 +192,10 @@ FactoryGirl.define do ...@@ -192,5 +192,10 @@ FactoryGirl.define do
trait :no_options do trait :no_options do
options { {} } options { {} }
end end
trait :non_playable do
status 'created'
self.when 'manual'
end
end end
end end
...@@ -32,5 +32,10 @@ FactoryGirl.define do ...@@ -32,5 +32,10 @@ FactoryGirl.define do
environment.update_attribute(:deployments, [deployment]) environment.update_attribute(:deployments, [deployment])
end end
end end
trait :non_playable do
status 'created'
self.when 'manual'
end
end end
end end
...@@ -19,6 +19,11 @@ describe('Actions Component', () => { ...@@ -19,6 +19,11 @@ describe('Actions Component', () => {
name: 'foo', name: 'foo',
play_path: '#', play_path: '#',
}, },
{
name: 'foo bar',
play_path: 'url',
playable: false,
},
]; ];
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve()); spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
...@@ -49,4 +54,14 @@ describe('Actions Component', () => { ...@@ -49,4 +54,14 @@ describe('Actions Component', () => {
expect(spy).toHaveBeenCalledWith(actionsMock[0].play_path); expect(spy).toHaveBeenCalledWith(actionsMock[0].play_path);
}); });
it('should render a disabled action when it\'s not playable', () => {
expect(
component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
).toEqual('disabled');
expect(
component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'),
).toEqual(true);
});
}); });
...@@ -15,6 +15,11 @@ describe('Pipelines Actions dropdown', () => { ...@@ -15,6 +15,11 @@ describe('Pipelines Actions dropdown', () => {
name: 'stop_review', name: 'stop_review',
path: '/root/review-app/builds/1893/play', path: '/root/review-app/builds/1893/play',
}, },
{
name: 'foo',
path: '#',
playable: false,
},
]; ];
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve()); spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
...@@ -59,4 +64,14 @@ describe('Pipelines Actions dropdown', () => { ...@@ -59,4 +64,14 @@ describe('Pipelines Actions dropdown', () => {
expect(component.$el.querySelector('.fa-spinner')).toEqual(null); expect(component.$el.querySelector('.fa-spinner')).toEqual(null);
}); });
it('should render a disabled action when it\'s not playable', () => {
expect(
component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'),
).toEqual('disabled');
expect(
component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'),
).toEqual(true);
});
}); });
...@@ -17,5 +17,9 @@ describe BuildActionEntity do ...@@ -17,5 +17,9 @@ describe BuildActionEntity do
it 'contains path to the action play' do it 'contains path to the action play' do
expect(subject[:path]).to include "builds/#{build.id}/play" expect(subject[:path]).to include "builds/#{build.id}/play"
end end
it 'contains whether it is playable' do
expect(subject[:playable]).to eq build.playable?
end
end end
end end
...@@ -24,6 +24,10 @@ describe BuildEntity do ...@@ -24,6 +24,10 @@ describe BuildEntity do
expect(subject).not_to include(/variables/) expect(subject).not_to include(/variables/)
end end
it 'contains whether it is playable' do
expect(subject[:playable]).to eq build.playable?
end
it 'contains timestamps' do it 'contains timestamps' do
expect(subject).to include(:created_at, :updated_at) expect(subject).to include(:created_at, :updated_at)
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