Commit 239ba73a authored by Rémy Coutable's avatar Rémy Coutable

Merge branch '20450-fix-ujs-actions-ee' into 'master'

20450 fix ujs actions ee

See merge request !1370
parents 623932ec d0340e41
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from './environments_table';
import EnvironmentsStore from '../stores/environments_store';
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('~/environments/services/environments_service');
const EnvironmentTable = require('./environments_table');
const EnvironmentsStore = require('~/environments/stores/environments_store');
require('~/vue_shared/components/table_pagination');
require('~/lib/utils/common_utils');
require('~/vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-component', {
export default Vue.component('environment-component', {
components: {
'environment-table': EnvironmentTable,
......@@ -162,7 +162,7 @@ module.exports = Vue.component('environment-component', {
<div class="content-list environments-container">
<div class="environments-list-loading text-center" v-if="isLoading">
<i class="fa fa-spinner fa-spin"></i>
<i class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</div>
<div class="blank-state blank-state-no-icon"
......
const Vue = require('vue');
const playIconSvg = require('icons/_icon_play.svg');
/* global Flash */
/* eslint-disable no-new */
module.exports = Vue.component('actions-component', {
import playIconSvg from 'icons/_icon_play.svg';
export default {
props: {
actions: {
type: Array,
required: false,
default: () => [],
},
service: {
type: Object,
required: true,
default: () => ({}),
},
},
data() {
return { playIconSvg };
return {
playIconSvg,
isLoading: false,
};
},
methods: {
onClickAction(endpoint) {
this.isLoading = true;
this.service.postAction(endpoint)
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.', 'alert');
});
},
},
template: `
<div class="btn-group" role="group">
<button class="dropdown btn btn-default dropdown-new" data-toggle="dropdown">
<button
class="dropdown btn btn-default dropdown-new js-dropdown-play-icon-container"
data-toggle="dropdown"
:disabled="isLoading">
<span>
<span class="js-dropdown-play-icon-container" v-html="playIconSvg"></span>
<i class="fa fa-caret-down"></i>
<span v-html="playIconSvg"></span>
<i class="fa fa-caret-down" aria-hidden="true"></i>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</span>
<ul class="dropdown-menu dropdown-menu-align-right">
<li v-for="action in actions">
<a :href="action.play_path"
data-method="post"
rel="nofollow"
class="js-manual-action-link">
<button
@click="onClickAction(action.play_path)"
class="js-manual-action-link no-btn">
${playIconSvg}
<span>
{{action.name}}
</span>
</a>
</button>
</li>
</ul>
</button>
</div>
`,
});
};
/**
* Renders the external url link in environments table.
*/
const Vue = require('vue');
module.exports = Vue.component('external-url-component', {
export default {
props: {
externalUrl: {
type: String,
......@@ -12,8 +10,12 @@ module.exports = Vue.component('external-url-component', {
},
template: `
<a class="btn external_url" :href="externalUrl" target="_blank">
<i class="fa fa-external-link"></i>
<a
class="btn external_url"
:href="externalUrl"
target="_blank"
title="Environment external URL">
<i class="fa fa-external-link" aria-hidden="true"></i>
</a>
`,
});
};
......@@ -4,20 +4,18 @@
* Renders a table row for each environment.
*/
const Vue = require('vue');
const Timeago = require('timeago.js');
require('../../lib/utils/text_utility');
require('../../vue_shared/components/commit');
const ActionsComponent = require('./environment_actions');
const ExternalUrlComponent = require('./environment_external_url');
const StopComponent = require('./environment_stop');
const RollbackComponent = require('./environment_rollback');
const TerminalButtonComponent = require('./environment_terminal_button');
import Timeago from 'timeago.js';
import ActionsComponent from './environment_actions';
import ExternalUrlComponent from './environment_external_url';
import StopComponent from './environment_stop';
import RollbackComponent from './environment_rollback';
import TerminalButtonComponent from './environment_terminal_button';
import '../../lib/utils/text_utility';
import '../../vue_shared/components/commit';
const timeagoInstance = new Timeago();
module.exports = Vue.component('environment-item', {
export default {
components: {
'commit-component': gl.CommitComponent,
......@@ -51,6 +49,12 @@ module.exports = Vue.component('environment-item', {
type: Function,
required: false,
},
service: {
type: Object,
required: true,
default: () => ({}),
},
},
computed: {
......@@ -509,22 +513,25 @@ module.exports = Vue.component('environment-item', {
<td class="environments-actions">
<div v-if="!model.isFolder" class="btn-group pull-right" role="group">
<actions-component v-if="hasManualActions && canCreateDeployment"
:service="service"
:actions="manualActions"/>
<external-url-component v-if="externalURL && canReadEnvironment"
:external-url="externalURL"/>
<stop-component v-if="hasStopAction && canCreateDeployment"
:stop-url="model.stop_path"/>
:stop-url="model.stop_path"
:service="service"/>
<terminal-button-component v-if="model && model.terminal_path"
:terminal-path="model.terminal_path"/>
<rollback-component v-if="canRetry && canCreateDeployment"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"/>
:retry-url="retryUrl"
:service="service"/>
</div>
</td>
</tr>
`,
});
};
/* global Flash */
/* eslint-disable no-new */
/**
* Renders Rollback or Re deploy button in environments table depending
* of the provided property `isLastDeployment`
* of the provided property `isLastDeployment`.
*
* Makes a post request when the button is clicked.
*/
const Vue = require('vue');
module.exports = Vue.component('rollback-component', {
export default {
props: {
retryUrl: {
type: String,
......@@ -15,16 +18,49 @@ module.exports = Vue.component('rollback-component', {
type: Boolean,
default: true,
},
service: {
type: Object,
required: true,
default: () => ({}),
},
},
data() {
return {
isLoading: false,
};
},
methods: {
onClick() {
this.isLoading = true;
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.', 'alert');
});
},
},
template: `
<a class="btn" :href="retryUrl" data-method="post" rel="nofollow">
<button type="button"
class="btn"
@click="onClick"
:disabled="isLoading">
<span v-if="isLastDeployment">
Re-deploy
</span>
<span v-else>
Rollback
</span>
</a>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
});
};
/* global Flash */
/* eslint-disable no-new, no-alert */
/**
* Renders the stop "button" that allows stop an environment.
* Used in environments table.
*/
const Vue = require('vue');
module.exports = Vue.component('stop-component', {
export default {
props: {
stopUrl: {
type: String,
default: '',
},
service: {
type: Object,
required: true,
default: () => ({}),
},
},
data() {
return {
isLoading: false,
};
},
methods: {
onClick() {
if (confirm('Are you sure you want to stop this environment?')) {
this.isLoading = true;
this.service.postAction(this.retryUrl)
.then(() => {
this.isLoading = false;
})
.catch(() => {
this.isLoading = false;
new Flash('An error occured while making the request.', 'alert');
});
}
},
},
template: `
<a class="btn stop-env-link"
:href="stopUrl"
data-confirm="Are you sure you want to stop this environment?"
data-method="post"
rel="nofollow">
<button type="button"
class="btn stop-env-link"
@click="onClick"
:disabled="isLoading"
title="Stop Environment">
<i class="fa fa-stop stop-env-icon" aria-hidden="true"></i>
</a>
<i v-if="isLoading" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
</button>
`,
});
};
......@@ -2,10 +2,9 @@
* Renders a terminal button to open a web terminal.
* Used in environments table.
*/
const Vue = require('vue');
const terminalIconSvg = require('icons/_icon_terminal.svg');
import terminalIconSvg from 'icons/_icon_terminal.svg';
module.exports = Vue.component('terminal-button-component', {
export default {
props: {
terminalPath: {
type: String,
......@@ -19,8 +18,9 @@ module.exports = Vue.component('terminal-button-component', {
template: `
<a class="btn terminal-button"
title="Open web terminal"
:href="terminalPath">
${terminalIconSvg}
</a>
`,
});
};
......@@ -4,12 +4,10 @@
* Dumb component used to render top level environments and
* the folder view.
*/
const Vue = require('vue');
const EnvironmentItem = require('./environment_item');
const DeployBoard = require('./deploy_board_component').default;
module.exports = Vue.component('environment-table-component', {
import EnvironmentItem from './environment_item';
import DeployBoard from './deploy_board_component';
export default {
components: {
EnvironmentItem,
DeployBoard,
......@@ -48,7 +46,7 @@ module.exports = Vue.component('environment-table-component', {
service: {
type: Object,
required: false,
required: true,
default: () => ({}),
},
},
......@@ -73,7 +71,8 @@ module.exports = Vue.component('environment-table-component', {
:model="model"
:can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
:toggleDeployBoard="toggleDeployBoard"></tr>
:toggleDeployBoard="toggleDeployBoard"
:service="service"></tr>
<tr v-if="model.hasDeployBoard && model.isDeployBoardVisible" class="js-deploy-board-row">
<td colspan="6" class="deploy-board-container">
......@@ -90,4 +89,4 @@ module.exports = Vue.component('environment-table-component', {
</tbody>
</table>
`,
});
};
const EnvironmentsComponent = require('./components/environment');
import EnvironmentsComponent from './components/environment';
$(() => {
window.gl = window.gl || {};
......
const EnvironmentsFolderComponent = require('./environments_folder_view');
import EnvironmentsFolderComponent from './environments_folder_view';
$(() => {
window.gl = window.gl || {};
......
/* eslint-disable no-new */
/* eslint-disable no-param-reassign, no-new */
/* global Flash */
import EnvironmentsService from '../services/environments_service';
import EnvironmentTable from '../components/environments_table';
import EnvironmentsStore from '../stores/environments_store';
const Flash = require('~/flash');
const Vue = window.Vue = require('vue');
window.Vue.use(require('vue-resource'));
const EnvironmentsService = require('~/environments//services/environments_service');
const EnvironmentTable = require('~/environments/components/environments_table');
const EnvironmentsStore = require('~/environments//stores/environments_store');
const Flash = require('~/flash');
require('~/vue_shared/components/table_pagination');
require('~/lib/utils/common_utils');
require('~/vue_shared/vue_resource_interceptor');
require('../../vue_shared/components/table_pagination');
require('../../lib/utils/common_utils');
require('../../vue_shared/vue_resource_interceptor');
module.exports = Vue.component('environment-folder-view', {
export default Vue.component('environment-folder-view', {
components: {
'environment-table': EnvironmentTable,
......@@ -183,13 +184,11 @@ module.exports = Vue.component('environment-folder-view', {
:commit-icon-svg="commitIconSvg"
:toggleDeployBoard="toggleDeployBoard"
:store="store"
:service="service">
</environment-table>
:service="service"/>
<table-pagination v-if="state.paginationInformation && state.paginationInformation.totalPages > 1"
:change="changePage"
:pageInfo="state.paginationInformation">
</table-pagination>
:pageInfo="state.paginationInformation"/>
</div>
</div>
</div>
......
/* eslint-disable class-methods-use-this*/
const Vue = require('vue');
/* eslint-disable class-methods-use-this */
import Vue from 'vue';
class EnvironmentsService {
export default class EnvironmentsService {
constructor(endpoint) {
this.environments = Vue.resource(endpoint);
}
......@@ -13,6 +13,8 @@ class EnvironmentsService {
getDeployBoard(endpoint) {
return Vue.http.get(endpoint);
}
}
module.exports = EnvironmentsService;
postAction(endpoint) {
return Vue.http.post(endpoint, {}, { emulateJSON: true });
}
}
require('~/lib/utils/common_utils');
import '~/lib/utils/common_utils';
/**
* Environments Store.
*
* Stores received environments, count of stopped environments and count of
* available environments.
*/
class EnvironmentsStore {
export default class EnvironmentsStore {
constructor() {
this.state = {};
this.state.environments = [];
......@@ -156,5 +157,3 @@ class EnvironmentsStore {
return this.state.environments;
}
}
module.exports = EnvironmentsStore;
......@@ -6,10 +6,6 @@ Vue.http.interceptors.push((request, next) => {
Vue.activeResources = Vue.activeResources ? Vue.activeResources + 1 : 1;
next((response) => {
if (typeof response.data === 'string') {
response.data = JSON.parse(response.data);
}
Vue.activeResources--;
});
});
......
......@@ -141,6 +141,14 @@
margin-right: 0;
}
}
.no-btn {
border: none;
background: none;
outline: none;
width: 100%;
text-align: left;
}
}
}
......
......@@ -111,10 +111,8 @@ feature 'Environments page', :feature, :js do
find('.js-dropdown-play-icon-container').click
expect(page).to have_content(action.name.humanize)
expect { click_link(action.name.humanize) }
expect { find('.js-manual-action-link').click }
.not_to change { Ci::Pipeline.count }
expect(action.reload).to be_pending
end
scenario 'does show build name and id' do
......@@ -158,12 +156,6 @@ feature 'Environments page', :feature, :js do
expect(page).to have_selector('.stop-env-link')
end
scenario 'starts build when stop button clicked' do
find('.stop-env-link').click
expect(page).to have_content('close_app')
end
context 'for reporter' do
let(:role) { :reporter }
......
const ActionsComponent = require('~/environments/components/environment_actions');
import Vue from 'vue';
import actionsComp from '~/environments/components/environment_actions';
describe('Actions Component', () => {
preloadFixtures('static/environments/element.html.raw');
let ActionsComponent;
let actionsMock;
let spy;
let component;
beforeEach(() => {
loadFixtures('static/environments/element.html.raw');
});
ActionsComponent = Vue.extend(actionsComp);
it('should render a dropdown with the provided actions', () => {
const actionsMock = [
actionsMock = [
{
name: 'bar',
play_path: 'https://gitlab.com/play',
......@@ -19,18 +21,27 @@ describe('Actions Component', () => {
},
];
const component = new ActionsComponent({
el: document.querySelector('.test-dom-element'),
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
component = new ActionsComponent({
propsData: {
actions: actionsMock,
service: {
postAction: spy,
},
},
});
}).$mount();
});
it('should render a dropdown with the provided actions', () => {
expect(
component.$el.querySelectorAll('.dropdown-menu li').length,
).toEqual(actionsMock.length);
expect(
component.$el.querySelector('.dropdown-menu li a').getAttribute('href'),
).toEqual(actionsMock[0].play_path);
});
it('should call the service when an action is clicked', () => {
component.$el.querySelector('.dropdown').click();
component.$el.querySelector('.js-manual-action-link').click();
expect(spy).toHaveBeenCalledWith(actionsMock[0].play_path);
});
});
const ExternalUrlComponent = require('~/environments/components/environment_external_url');
import Vue from 'vue';
import externalUrlComp from '~/environments/components/environment_external_url';
describe('External URL Component', () => {
preloadFixtures('static/environments/element.html.raw');
let ExternalUrlComponent;
beforeEach(() => {
loadFixtures('static/environments/element.html.raw');
ExternalUrlComponent = Vue.extend(externalUrlComp);
});
it('should link to the provided externalUrl prop', () => {
const externalURL = 'https://gitlab.com';
const component = new ExternalUrlComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
externalUrl: externalURL,
},
});
}).$mount();
expect(component.$el.getAttribute('href')).toEqual(externalURL);
expect(component.$el.querySelector('fa-external-link')).toBeDefined();
......
window.timeago = require('timeago.js');
const EnvironmentItem = require('~/environments/components/environment_item');
import 'timeago.js';
import Vue from 'vue';
import environmentItemComp from '~/environments/components/environment_item';
describe('Environment item', () => {
preloadFixtures('static/environments/table.html.raw');
let EnvironmentItem;
beforeEach(() => {
loadFixtures('static/environments/table.html.raw');
EnvironmentItem = Vue.extend(environmentItemComp);
});
describe('When item is folder', () => {
......@@ -21,7 +23,6 @@ describe('Environment item', () => {
};
component = new EnvironmentItem({
el: document.querySelector('tr#environment-row'),
propsData: {
model: mockItem,
canCreateDeployment: false,
......@@ -30,7 +31,7 @@ describe('Environment item', () => {
store: {},
service: {},
},
});
}).$mount();
});
it('Should render folder icon and name', () => {
......@@ -112,7 +113,6 @@ describe('Environment item', () => {
};
component = new EnvironmentItem({
el: document.querySelector('tr#environment-row'),
propsData: {
model: environment,
canCreateDeployment: true,
......@@ -121,7 +121,7 @@ describe('Environment item', () => {
store: {},
service: {},
},
});
}).$mount();
});
it('should render environment name', () => {
......
const RollbackComponent = require('~/environments/components/environment_rollback');
import Vue from 'vue';
import rollbackComp from '~/environments/components/environment_rollback';
describe('Rollback Component', () => {
preloadFixtures('static/environments/element.html.raw');
const retryURL = 'https://gitlab.com/retry';
let RollbackComponent;
let spy;
beforeEach(() => {
loadFixtures('static/environments/element.html.raw');
RollbackComponent = Vue.extend(rollbackComp);
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
});
it('Should link to the provided retryUrl', () => {
it('Should render Re-deploy label when isLastDeployment is true', () => {
const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
isLastDeployment: true,
service: {
postAction: spy,
},
},
});
}).$mount();
expect(component.$el.getAttribute('href')).toEqual(retryURL);
expect(component.$el.querySelector('span').textContent).toContain('Re-deploy');
});
it('Should render Re-deploy label when isLastDeployment is true', () => {
it('Should render Rollback label when isLastDeployment is false', () => {
const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
isLastDeployment: true,
isLastDeployment: false,
service: {
postAction: spy,
},
},
});
}).$mount();
expect(component.$el.querySelector('span').textContent).toContain('Re-deploy');
expect(component.$el.querySelector('span').textContent).toContain('Rollback');
});
it('Should render Rollback label when isLastDeployment is false', () => {
it('should call the service when the button is clicked', () => {
const component = new RollbackComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
retryUrl: retryURL,
isLastDeployment: false,
service: {
postAction: spy,
},
},
});
}).$mount();
expect(component.$el.querySelector('span').textContent).toContain('Rollback');
component.$el.click();
expect(spy).toHaveBeenCalledWith(retryURL);
});
});
const Vue = require('vue');
require('~/flash');
const EnvironmentsComponent = require('~/environments/components/environment');
const { environment } = require('./mock_data');
import Vue from 'vue';
import '~/flash';
import EnvironmentsComponent from '~/environments/components/environment';
import { environment } from './mock_data';
describe('Environment', () => {
preloadFixtures('static/environments/environments.html.raw');
......
const StopComponent = require('~/environments/components/environment_stop');
import Vue from 'vue';
import stopComp from '~/environments/components/environment_stop';
describe('Stop Component', () => {
preloadFixtures('static/environments/element.html.raw');
let stopURL;
let StopComponent;
let component;
let spy;
const stopURL = '/stop';
beforeEach(() => {
loadFixtures('static/environments/element.html.raw');
StopComponent = Vue.extend(stopComp);
spy = jasmine.createSpy('spy').and.returnValue(Promise.resolve());
spyOn(window, 'confirm').and.returnValue(true);
stopURL = '/stop';
component = new StopComponent({
el: document.querySelector('.test-dom-element'),
propsData: {
stopUrl: stopURL,
service: {
postAction: spy,
},
},
});
}).$mount();
});
it('should link to the provided URL', () => {
expect(component.$el.getAttribute('href')).toEqual(stopURL);
it('should render a button to stop the environment', () => {
expect(component.$el.tagName).toEqual('BUTTON');
expect(component.$el.getAttribute('title')).toEqual('Stop Environment');
});
it('should have a data-confirm attribute', () => {
expect(component.$el.getAttribute('data-confirm')).toEqual('Are you sure you want to stop this environment?');
it('should call the service when an action is clicked', () => {
component.$el.click();
expect(spy).toHaveBeenCalled();
});
});
const EnvironmentTable = require('~/environments/components/environments_table');
import Vue from 'vue';
import environmentTableComp from '~/environments/components/environments_table';
describe('Environment item', () => {
preloadFixtures('static/environments/element.html.raw');
let EnvironmentTable;
beforeEach(() => {
loadFixtures('static/environments/element.html.raw');
EnvironmentTable = Vue.extend(environmentTableComp);
});
it('Should render a table', () => {
......@@ -25,7 +27,7 @@ describe('Environment item', () => {
store: {},
service: {},
},
});
}).$mount();
expect(component.$el.tagName).toEqual('TABLE');
});
......@@ -60,7 +62,7 @@ describe('Environment item', () => {
store: {},
service: {},
},
});
}).$mount();
expect(component.$el.querySelector('.js-deploy-board-row')).toBeDefined();
expect(
......@@ -100,7 +102,7 @@ describe('Environment item', () => {
store: {},
service: {},
},
});
}).$mount();
component.$el.querySelector('.deploy-board-icon').click();
......
import Vue from 'vue';
import terminalComp from '~/environments/components/environment_terminal_button';
describe('Stop Component', () => {
let TerminalComponent;
let component;
const terminalPath = '/path';
beforeEach(() => {
TerminalComponent = Vue.extend(terminalComp);
component = new TerminalComponent({
propsData: {
terminalPath,
},
}).$mount();
});
it('should render a link to open a web terminal with the provided path', () => {
expect(component.$el.tagName).toEqual('A');
expect(component.$el.getAttribute('title')).toEqual('Open web terminal');
expect(component.$el.getAttribute('href')).toEqual(terminalPath);
});
});
const Store = require('~/environments/stores/environments_store');
const { serverData, deployBoardMockData } = require('./mock_data');
import Store from '~/environments/stores/environments_store';
import { serverData, deployBoardMockData } from './mock_data';
(() => {
describe('Environments Store', () => {
......
const Vue = require('vue');
require('~/flash');
const EnvironmentsFolderViewComponent = require('~/environments/folder/environments_folder_view');
const { environmentsList } = require('../mock_data');
import Vue from 'vue';
import '~/flash';
import EnvironmentsFolderViewComponent from '~/environments/folder/environments_folder_view';
import { environmentsList } from '../mock_data';
describe('Environments Folder View', () => {
preloadFixtures('static/environments/environments_folder_view.html.raw');
......
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