diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
new file mode 100644
index 00000000000..7bef2e97349
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -0,0 +1,144 @@
+
+
+
+
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
deleted file mode 100644
index c7f992384c8..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_deployment.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { getTimeago } from '~/lib/utils/datetime_utility';
-import { visitUrl } from '../../lib/utils/url_utility';
-import Flash from '../../flash';
-import MemoryUsage from './memory_usage.vue';
-import StatusIcon from './mr_widget_status_icon.vue';
-import MRWidgetService from '../services/mr_widget_service';
-
-export default {
- name: 'MRWidgetDeployment',
- props: {
- mr: { type: Object, required: true },
- service: { type: Object, required: true },
- },
- components: {
- MemoryUsage,
- StatusIcon,
- },
- methods: {
- formatDate(date) {
- return getTimeago().format(date);
- },
- hasExternalUrls(deployment = {}) {
- return deployment.external_url && deployment.external_url_formatted;
- },
- hasDeploymentTime(deployment = {}) {
- return deployment.deployed_at && deployment.deployed_at_formatted;
- },
- hasDeploymentMeta(deployment = {}) {
- return deployment.url && deployment.name;
- },
- stopEnvironment(deployment) {
- const msg = 'Are you sure you want to stop this environment?';
- const isConfirmed = confirm(msg); // eslint-disable-line
-
- if (isConfirmed) {
- MRWidgetService.stopEnvironment(deployment.stop_url)
- .then(res => res.data)
- .then((data) => {
- if (data.redirect_url) {
- visitUrl(data.redirect_url);
- }
- })
- .catch(() => {
- new Flash('Something went wrong while stopping this environment. Please try again.'); // eslint-disable-line
- });
- }
- },
- },
- template: `
-
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 20624aad0ad..efbe1c96d1c 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -14,7 +14,7 @@ export { default as SmartInterval } from '~/smart_interval';
export { default as WidgetHeader } from './components/mr_widget_header.vue';
export { default as WidgetMergeHelp } from './components/mr_widget_merge_help.vue';
export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue';
-export { default as WidgetDeployment } from './components/mr_widget_deployment';
+export { default as Deployment } from './components/deployment.vue';
export { default as WidgetMaintainerEdit } from './components/mr_widget_maintainer_edit.vue';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue';
export { default as MergedState } from './components/states/mr_widget_merged.vue';
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
index cc8bc6af1e1..169adfe0a1d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js
@@ -5,7 +5,7 @@ import {
WidgetHeader,
WidgetMergeHelp,
WidgetPipeline,
- WidgetDeployment,
+ Deployment,
WidgetMaintainerEdit,
WidgetRelatedLinks,
MergedState,
@@ -67,9 +67,6 @@ export default {
shouldRenderRelatedLinks() {
return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState;
},
- shouldRenderDeployments() {
- return this.mr.deployments.length;
- },
shouldRenderSourceBranchRemovalStatus() {
return !this.mr.canRemoveSourceBranch && this.mr.shouldRemoveSourceBranch &&
(!this.mr.isNothingToMergeState && !this.mr.isMergedState);
@@ -216,7 +213,7 @@ export default {
'mr-widget-header': WidgetHeader,
'mr-widget-merge-help': WidgetMergeHelp,
'mr-widget-pipeline': WidgetPipeline,
- 'mr-widget-deployment': WidgetDeployment,
+ Deployment,
'mr-widget-maintainer-edit': WidgetMaintainerEdit,
'mr-widget-related-links': WidgetRelatedLinks,
'mr-widget-merged': MergedState,
@@ -250,10 +247,11 @@ export default {
:ci-status="mr.ciStatus"
:has-ci="mr.hasCI"
/>
-
+
*:not(:last-child) {
+ margin-right: .3em;
+ }
+}
+
+.deploy-link {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 100px;
+ max-width: 150px;
+
+ @media (min-width: $screen-xs) {
+ min-width: 0;
+ max-width: 100%;
+ }
+}
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index 3abe363d523..f744d7941f5 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -22,7 +22,7 @@ describe 'Merge request > User sees deployment widget', :js do
wait_for_requests
expect(page).to have_content("Deployed to #{environment.name}")
- expect(find('.js-deploy-time')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
+ expect(find('.js-deploy-time')['data-original-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium))
end
context 'with stop action' do
diff --git a/spec/javascripts/vue_mr_widget/components/deployment_spec.js b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
new file mode 100644
index 00000000000..ff8d54c029f
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/deployment_spec.js
@@ -0,0 +1,172 @@
+import Vue from 'vue';
+import * as urlUtils from '~/lib/utils/url_utility';
+import deploymentComponent from '~/vue_merge_request_widget/components/deployment.vue';
+import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
+import { getTimeago } from '~/lib/utils/datetime_utility';
+
+const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+};
+const createComponent = () => {
+ const Component = Vue.extend(deploymentComponent);
+
+ return new Component({
+ el: document.createElement('div'),
+ propsData: { deployment: { ...deploymentMockData } },
+ });
+};
+
+describe('Deployment component', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('deployTimeago', () => {
+ it('return formatted date', () => {
+ const readable = getTimeago().format(deploymentMockData.deployed_at);
+ expect(vm.deployTimeago).toEqual(readable);
+ });
+ });
+
+ describe('hasExternalUrls', () => {
+ it('should return true', () => {
+ expect(vm.hasExternalUrls).toEqual(true);
+ });
+
+ it('should return false when deployment has no external_url_formatted', () => {
+ vm.deployment.external_url_formatted = null;
+
+ expect(vm.hasExternalUrls).toEqual(false);
+ });
+
+ it('should return false when deployment has no external_url', () => {
+ vm.deployment.external_url = null;
+
+ expect(vm.hasExternalUrls).toEqual(false);
+ });
+ });
+
+ describe('hasDeploymentTime', () => {
+ it('should return true', () => {
+ expect(vm.hasDeploymentTime).toEqual(true);
+ });
+
+ it('should return false when deployment has no deployed_at', () => {
+ vm.deployment.deployed_at = null;
+
+ expect(vm.hasDeploymentTime).toEqual(false);
+ });
+
+ it('should return false when deployment has no deployed_at_formatted', () => {
+ vm.deployment.deployed_at_formatted = null;
+
+ expect(vm.hasDeploymentTime).toEqual(false);
+ });
+ });
+
+ describe('hasDeploymentMeta', () => {
+ it('should return true', () => {
+ expect(vm.hasDeploymentMeta).toEqual(true);
+ });
+
+ it('should return false when deployment has no url', () => {
+ vm.deployment.url = null;
+
+ expect(vm.hasDeploymentMeta).toEqual(false);
+ });
+
+ it('should return false when deployment has no name', () => {
+ vm.deployment.name = null;
+
+ expect(vm.hasDeploymentMeta).toEqual(false);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('stopEnvironment', () => {
+ const url = '/foo/bar';
+ const returnPromise = () => new Promise((resolve) => {
+ resolve({
+ data: {
+ redirect_url: url,
+ },
+ });
+ });
+ const mockStopEnvironment = () => {
+ vm.stopEnvironment(deploymentMockData);
+ return vm;
+ };
+
+ it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
+ spyOn(window, 'confirm').and.returnValue(true);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
+ spyOn(urlUtils, 'visitUrl').and.returnValue(true);
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
+ setTimeout(() => {
+ expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
+ done();
+ }, 333);
+ });
+
+ it('should show a confirm dialog but should not work if the dialog is rejected', () => {
+ spyOn(window, 'confirm').and.returnValue(false);
+ spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
+ vm = mockStopEnvironment();
+
+ expect(window.confirm).toHaveBeenCalled();
+ expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('template', () => {
+ let el;
+
+ beforeEach(() => {
+ vm = createComponent(deploymentMockData);
+ el = vm.$el;
+ });
+
+ it('renders deployment name', () => {
+ expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deploymentMockData.url);
+ expect(el.querySelector('.js-deploy-meta').innerText).toContain(deploymentMockData.name);
+ });
+
+ it('renders external URL', () => {
+ expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deploymentMockData.external_url);
+ expect(el.querySelector('.js-deploy-url').innerText).toContain(deploymentMockData.external_url_formatted);
+ });
+
+ it('renders stop button', () => {
+ expect(el.querySelector('.btn')).not.toBeNull();
+ });
+
+ it('renders deployment time', () => {
+ expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.deployTimeago);
+ });
+
+ it('renders metrics component', () => {
+ expect(el.querySelector('.js-mr-memory-usage')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
deleted file mode 100644
index 6a59dc3c87e..00000000000
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_deployment_spec.js
+++ /dev/null
@@ -1,179 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import deploymentComponent from '~/vue_merge_request_widget/components/mr_widget_deployment';
-import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
-import { getTimeago } from '~/lib/utils/datetime_utility';
-
-const deploymentMockData = [
- {
- id: 15,
- name: 'review/diplo',
- url: '/root/acets-review-apps/environments/15',
- stop_url: '/root/acets-review-apps/environments/15/stop',
- metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
- metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
- external_url: 'http://diplo.',
- external_url_formatted: 'diplo.',
- deployed_at: '2017-03-22T22:44:42.258Z',
- deployed_at_formatted: 'Mar 22, 2017 10:44pm',
- },
-];
-const createComponent = () => {
- const Component = Vue.extend(deploymentComponent);
- const mr = {
- deployments: deploymentMockData,
- };
- const service = {};
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr, service },
- });
-};
-
-describe('MRWidgetDeployment', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr, service } = deploymentComponent.props;
-
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
-
- expect(service.type instanceof Object).toBeTruthy();
- expect(service.required).toBeTruthy();
- });
- });
-
- describe('methods', () => {
- let vm = createComponent();
- const deployment = deploymentMockData[0];
-
- describe('formatDate', () => {
- it('should work', () => {
- const readable = getTimeago().format(deployment.deployed_at);
- expect(vm.formatDate(deployment.deployed_at)).toEqual(readable);
- });
- });
-
- describe('hasExternalUrls', () => {
- it('should return true', () => {
- expect(vm.hasExternalUrls(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasExternalUrls()).toBeFalsy();
- expect(vm.hasExternalUrls({ external_url: 'Diplo' })).toBeFalsy();
- expect(vm.hasExternalUrls({ external_url_formatted: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('hasDeploymentTime', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentTime(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasDeploymentTime()).toBeFalsy();
- expect(vm.hasDeploymentTime({ deployed_at: 'Diplo' })).toBeFalsy();
- expect(vm.hasDeploymentTime({ deployed_at_formatted: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('hasDeploymentMeta', () => {
- it('should return true', () => {
- expect(vm.hasDeploymentMeta(deployment)).toBeTruthy();
- });
-
- it('should return false when there is not enough information', () => {
- expect(vm.hasDeploymentMeta()).toBeFalsy();
- expect(vm.hasDeploymentMeta({ url: 'Diplo' })).toBeFalsy();
- expect(vm.hasDeploymentMeta({ name: 'Diplo' })).toBeFalsy();
- });
- });
-
- describe('stopEnvironment', () => {
- const url = '/foo/bar';
- const returnPromise = () => new Promise((resolve) => {
- resolve({
- data: {
- redirect_url: url,
- },
- });
- });
- const mockStopEnvironment = () => {
- vm.stopEnvironment(deploymentMockData);
- return vm;
- };
-
- it('should show a confirm dialog and call service.stopEnvironment when confirmed', (done) => {
- spyOn(window, 'confirm').and.returnValue(true);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(true));
- spyOn(urlUtils, 'visitUrl').and.returnValue(true);
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
- setTimeout(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalledWith(url);
- done();
- }, 333);
- });
-
- it('should show a confirm dialog but should not work if the dialog is rejected', () => {
- spyOn(window, 'confirm').and.returnValue(false);
- spyOn(MRWidgetService, 'stopEnvironment').and.returnValue(returnPromise(false));
- vm = mockStopEnvironment();
-
- expect(window.confirm).toHaveBeenCalled();
- expect(MRWidgetService.stopEnvironment).not.toHaveBeenCalled();
- });
- });
- });
-
- describe('template', () => {
- let vm;
- let el;
- const [deployment] = deploymentMockData;
-
- beforeEach(() => {
- vm = createComponent(deploymentMockData);
- el = vm.$el;
- });
-
- it('should render template elements correctly', () => {
- expect(el.classList.contains('mr-widget-heading')).toBeTruthy();
- expect(el.querySelector('.js-icon-link')).toBeDefined();
- expect(el.querySelector('.js-deploy-meta').getAttribute('href')).toEqual(deployment.url);
- expect(el.querySelector('.js-deploy-meta').innerText).toContain(deployment.name);
- expect(el.querySelector('.js-deploy-url').getAttribute('href')).toEqual(deployment.external_url);
- expect(el.querySelector('.js-deploy-url').innerText).toContain(deployment.external_url_formatted);
- expect(el.querySelector('.js-deploy-time').innerText).toContain(vm.formatDate(deployment.deployed_at));
- expect(el.querySelector('.js-mr-memory-usage')).toBeDefined();
- expect(el.querySelector('button')).toBeDefined();
- });
-
- it('should list multiple deployments', (done) => {
- vm.mr.deployments.push(deployment);
- vm.mr.deployments.push(deployment);
-
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.ci-widget').length).toEqual(3);
- expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(3);
- done();
- });
- });
-
- it('should not have some elements when there is not enough data', (done) => {
- vm.mr.deployments = [{}];
-
- Vue.nextTick(() => {
- expect(el.querySelectorAll('.js-deploy-meta').length).toEqual(0);
- expect(el.querySelectorAll('.js-deploy-url').length).toEqual(0);
- expect(el.querySelectorAll('.js-deploy-time').length).toEqual(0);
- expect(el.querySelectorAll('.js-mr-memory-usage').length).toEqual(0);
- expect(el.querySelectorAll('.button').length).toEqual(0);
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
index 32876ca66ac..e55c7649d40 100644
--- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
+++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js
@@ -123,17 +123,6 @@ describe('mrWidgetOptions', () => {
expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
});
});
-
- describe('shouldRenderDeployments', () => {
- it('should return false for the initial data', () => {
- expect(vm.shouldRenderDeployments).toBeFalsy();
- });
-
- it('should return true if there is deployments', () => {
- vm.mr.deployments.push({}, {});
- expect(vm.shouldRenderDeployments).toBeTruthy();
- });
- });
});
describe('methods', () => {
@@ -189,16 +178,16 @@ describe('mrWidgetOptions', () => {
describe('fetchDeployments', () => {
it('should fetch deployments', (done) => {
- spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ deployment: 1 }]));
+ spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }]));
vm.fetchDeployments();
setTimeout(() => {
expect(vm.service.fetchDeployments).toHaveBeenCalled();
expect(vm.mr.deployments.length).toEqual(1);
- expect(vm.mr.deployments[0].deployment).toEqual(1);
+ expect(vm.mr.deployments[0].id).toBe(1);
done();
- }, 333);
+ });
});
});
@@ -368,34 +357,6 @@ describe('mrWidgetOptions', () => {
});
});
- describe('components', () => {
- it('should register all components', () => {
- const comps = mrWidgetOptions.components;
- expect(comps['mr-widget-header']).toBeDefined();
- expect(comps['mr-widget-merge-help']).toBeDefined();
- expect(comps['mr-widget-pipeline']).toBeDefined();
- expect(comps['mr-widget-deployment']).toBeDefined();
- expect(comps['mr-widget-related-links']).toBeDefined();
- expect(comps['mr-widget-merged']).toBeDefined();
- expect(comps['mr-widget-closed']).toBeDefined();
- expect(comps['mr-widget-merging']).toBeDefined();
- expect(comps['mr-widget-failed-to-merge']).toBeDefined();
- expect(comps['mr-widget-wip']).toBeDefined();
- expect(comps['mr-widget-archived']).toBeDefined();
- expect(comps['mr-widget-conflicts']).toBeDefined();
- expect(comps['mr-widget-nothing-to-merge']).toBeDefined();
- expect(comps['mr-widget-not-allowed']).toBeDefined();
- expect(comps['mr-widget-missing-branch']).toBeDefined();
- expect(comps['mr-widget-ready-to-merge']).toBeDefined();
- expect(comps['mr-widget-checking']).toBeDefined();
- expect(comps['mr-widget-unresolved-discussions']).toBeDefined();
- expect(comps['mr-widget-pipeline-blocked']).toBeDefined();
- expect(comps['mr-widget-pipeline-failed']).toBeDefined();
- expect(comps['mr-widget-merge-when-pipeline-succeeds']).toBeDefined();
- expect(comps['mr-widget-maintainer-edit']).toBeDefined();
- });
- });
-
describe('rendering relatedLinks', () => {
beforeEach((done) => {
vm.mr.relatedLinks = {
@@ -454,4 +415,34 @@ describe('mrWidgetOptions', () => {
});
});
});
+
+ describe('rendering deployments', () => {
+ const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ };
+
+ beforeEach((done) => {
+ vm.mr.deployments.push({
+ ...deploymentMockData,
+ }, {
+ ...deploymentMockData,
+ id: deploymentMockData.id + 1,
+ });
+
+ vm.$nextTick(done);
+ });
+
+ it('renders multiple deployments', () => {
+ expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
+ });
+ });
});