Commit cdbe1fbd authored by Sarah Groff Hennigh-Palermo's avatar Sarah Groff Hennigh-Palermo Committed by Phil Hughes

Renamed methods to expose playable build

Removed build_manual_actions method.
Using playable? check to expose build.
Added related tests.
parent 3511db37
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
import { __ } from '~/locale';
import timeagoMixin from '../../vue_shared/mixins/timeago';
import LoadingButton from '../../vue_shared/components/loading_button.vue';
import { visitUrl } from '../../lib/utils/url_utility';
import createFlash from '../../flash';
import MemoryUsage from './memory_usage.vue';
import StatusIcon from './mr_widget_status_icon.vue';
import ReviewAppLink from './review_app_link.vue';
import MRWidgetService from '../services/mr_widget_service';
export default {
// name: 'Deployment' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
name: 'Deployment',
components: {
LoadingButton,
MemoryUsage,
StatusIcon,
Icon,
TooltipOnTruncate,
FilteredSearchDropdown,
ReviewAppLink,
VisualReviewAppLink: () =>
import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'),
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
props: {
deployment: {
type: Object,
required: true,
},
showMetrics: {
type: Boolean,
required: true,
},
showVisualReviewApp: {
type: Boolean,
required: false,
default: false,
},
visualReviewAppMeta: {
type: Object,
required: false,
default: () => ({
sourceProjectId: '',
sourceProjectPath: '',
mergeRequestId: '',
appUrl: '',
}),
},
},
deployedTextMap: {
running: __('Deploying to'),
success: __('Deployed to'),
failed: __('Failed to deploy to'),
created: __('Will deploy to'),
canceled: __('Failed to deploy to'),
},
data() {
return {
isStopping: false,
};
},
computed: {
deployTimeago() {
return this.timeFormated(this.deployment.deployed_at);
},
deploymentExternalUrl() {
if (this.deployment.changes && this.deployment.changes.length === 1) {
return this.deployment.changes[0].external_url;
}
return this.deployment.external_url;
},
hasExternalUrls() {
return Boolean(this.deployment.external_url && this.deployment.external_url_formatted);
},
hasDeploymentTime() {
return Boolean(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
},
hasDeploymentMeta() {
return Boolean(this.deployment.url && this.deployment.name);
},
hasMetrics() {
return Boolean(this.deployment.metrics_url);
},
deployedText() {
return this.$options.deployedTextMap[this.deployment.status];
},
isDeployInProgress() {
return this.deployment.status === 'running';
},
deployInProgressTooltip() {
return this.isDeployInProgress
? __('Stopping this environment is currently not possible as a deployment is in progress')
: '';
},
shouldRenderDropdown() {
return this.deployment.changes && this.deployment.changes.length > 1;
},
showMemoryUsage() {
return this.hasMetrics && this.showMetrics;
},
},
methods: {
stopEnvironment() {
const msg = __('Are you sure you want to stop this environment?');
const isConfirmed = confirm(msg); // eslint-disable-line
if (isConfirmed) {
this.isStopping = true;
MRWidgetService.stopEnvironment(this.deployment.stop_url)
.then(res => res.data)
.then(data => {
if (data.redirect_url) {
visitUrl(data.redirect_url);
}
this.isStopping = false;
})
.catch(() => {
createFlash(
__('Something went wrong while stopping this environment. Please try again.'),
);
this.isStopping = false;
});
}
},
},
};
</script>
<template>
<div class="deploy-heading">
<div class="ci-widget media">
<div class="media-body">
<div class="deploy-body">
<div class="js-deployment-info deployment-info">
<template v-if="hasDeploymentMeta">
<span> {{ deployedText }} </span>
<tooltip-on-truncate
:title="deployment.name"
truncate-target="child"
class="deploy-link label-truncate"
>
<a
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="js-deploy-meta"
>
{{ deployment.name }}
</a>
</tooltip-on-truncate>
</template>
<span
v-if="hasDeploymentTime"
v-gl-tooltip
:title="deployment.deployed_at_formatted"
class="js-deploy-time"
>
{{ deployTimeago }}
</span>
<memory-usage
v-if="showMemoryUsage"
:metrics-url="deployment.metrics_url"
:metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
<div>
<template v-if="hasExternalUrls">
<filtered-search-dropdown
v-if="shouldRenderDropdown"
class="js-mr-wigdet-deployment-dropdown inline"
:items="deployment.changes"
:main-action-link="deploymentExternalUrl"
filter-key="path"
>
<template slot="mainAction" slot-scope="slotProps">
<review-app-link
:link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/>
</template>
<template slot="result" slot-scope="slotProps">
<a
:href="slotProps.result.external_url"
target="_blank"
rel="noopener noreferrer nofollow"
class="menu-item"
>
<strong class="str-truncated-100 append-bottom-0 d-block">
{{ slotProps.result.path }}
</strong>
<p class="text-secondary str-truncated-100 append-bottom-0 d-block">
{{ slotProps.result.external_url }}
</p>
</a>
</template>
</filtered-search-dropdown>
<template v-else>
<review-app-link
:link="deploymentExternalUrl"
css-class="js-deploy-url deploy-link btn btn-default btn-sm inline"
/>
</template>
<visual-review-app-link
v-if="showVisualReviewApp"
:link="deploymentExternalUrl"
:app-metadata="visualReviewAppMeta"
/>
</template>
<span
v-if="deployment.stop_url"
v-gl-tooltip
:title="deployInProgressTooltip"
class="d-inline-block"
tabindex="0"
>
<loading-button
:loading="isStopping"
:disabled="isDeployInProgress"
:title="__('Stop environment')"
container-class="js-stop-env btn btn-default btn-sm inline prepend-left-4"
@click="stopEnvironment"
>
<icon name="stop" />
</loading-button>
</span>
</div>
</div>
</div>
</div>
</div>
</template>
// DEPLOYMENT STATUSES
export const CREATED = 'created';
export const MANUAL_DEPLOY = 'manual_deploy';
export const WILL_DEPLOY = 'will_deploy';
export const RUNNING = 'running';
export const SUCCESS = 'success';
export const FAILED = 'failed';
export const CANCELED = 'canceled';
<script>
import { GlTooltipDirective } from '@gitlab/ui';
import DeploymentInfo from './deployment_info.vue';
import DeploymentViewButton from './deployment_view_button.vue';
import DeploymentStopButton from './deployment_stop_button.vue';
import { MANUAL_DEPLOY, WILL_DEPLOY, CREATED, RUNNING, SUCCESS } from './constants';
export default {
// name: 'Deployment' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
name: 'Deployment',
components: {
DeploymentInfo,
DeploymentStopButton,
DeploymentViewButton,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
deployment: {
type: Object,
required: true,
},
showMetrics: {
type: Boolean,
required: true,
},
showVisualReviewApp: {
type: Boolean,
required: false,
default: false,
},
visualReviewAppMeta: {
type: Object,
required: false,
default: () => ({
sourceProjectId: '',
sourceProjectPath: '',
mergeRequestId: '',
appUrl: '',
}),
},
},
computed: {
canBeManuallyDeployed() {
return this.computedDeploymentStatus === MANUAL_DEPLOY;
},
computedDeploymentStatus() {
if (this.deployment.status === CREATED) {
return this.isManual ? MANUAL_DEPLOY : WILL_DEPLOY;
}
return this.deployment.status;
},
hasExternalUrls() {
return Boolean(this.deployment.external_url && this.deployment.external_url_formatted);
},
hasPreviousDeployment() {
return Boolean(!this.isCurrent && this.deployment.deployed_at);
},
isCurrent() {
return this.computedDeploymentStatus === SUCCESS;
},
isManual() {
return Boolean(
this.deployment.details &&
this.deployment.details.playable_build &&
this.deployment.details.playable_build.play_path,
);
},
isDeployInProgress() {
return this.deployment.status === RUNNING;
},
},
};
</script>
<template>
<div class="deploy-heading">
<div class="ci-widget media">
<div class="media-body">
<div class="deploy-body">
<deployment-info
:computed-deployment-status="computedDeploymentStatus"
:deployment="deployment"
:show-metrics="showMetrics"
/>
<div>
<!-- show appropriate version of review app button -->
<deployment-view-button
v-if="hasExternalUrls"
:is-current="isCurrent"
:deployment="deployment"
:show-visual-review-app="showVisualReviewApp"
:visual-review-app-metadata="visualReviewAppMeta"
/>
<!-- if it is stoppable, show stop -->
<deployment-stop-button
v-if="deployment.stop_url"
:is-deploy-in-progress="isDeployInProgress"
:stop-url="deployment.stop_url"
/>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import MemoryUsage from './memory_usage.vue';
import { MANUAL_DEPLOY, WILL_DEPLOY, RUNNING, SUCCESS, FAILED, CANCELED } from './constants';
export default {
name: 'DeploymentInfo',
components: {
GlLink,
MemoryUsage,
TooltipOnTruncate,
},
directives: {
GlTooltip: GlTooltipDirective,
},
mixins: [timeagoMixin],
props: {
computedDeploymentStatus: {
type: String,
required: true,
},
deployment: {
type: Object,
required: true,
},
showMetrics: {
type: Boolean,
required: true,
},
},
deployedTextMap: {
[MANUAL_DEPLOY]: __('Can deploy manually to'),
[WILL_DEPLOY]: __('Will deploy to'),
[RUNNING]: __('Deploying to'),
[SUCCESS]: __('Deployed to'),
[FAILED]: __('Failed to deploy to'),
[CANCELED]: __('Canceled deploy to'),
},
computed: {
deployTimeago() {
return this.timeFormated(this.deployment.deployed_at);
},
deployedText() {
return this.$options.deployedTextMap[this.computedDeploymentStatus];
},
hasDeploymentTime() {
return Boolean(this.deployment.deployed_at && this.deployment.deployed_at_formatted);
},
hasDeploymentMeta() {
return Boolean(this.deployment.url && this.deployment.name);
},
hasMetrics() {
return Boolean(this.deployment.metrics_url);
},
showMemoryUsage() {
return this.hasMetrics && this.showMetrics;
},
},
};
</script>
<template>
<div class="js-deployment-info deployment-info">
<template v-if="hasDeploymentMeta">
<span>{{ deployedText }}</span>
<tooltip-on-truncate
:title="deployment.name"
truncate-target="child"
class="deploy-link label-truncate"
>
<gl-link
:href="deployment.url"
target="_blank"
rel="noopener noreferrer nofollow"
class="js-deploy-meta gl-font-size-12"
>
{{ deployment.name }}
</gl-link>
</tooltip-on-truncate>
</template>
<span
v-if="hasDeploymentTime"
v-gl-tooltip
:title="deployment.deployed_at_formatted"
class="js-deploy-time"
>
{{ deployTimeago }}
</span>
<memory-usage
v-if="showMemoryUsage"
:metrics-url="deployment.metrics_url"
:metrics-monitoring-url="deployment.metrics_monitoring_url"
/>
</div>
</template>
<script>
import { __ } from '~/locale';
import { GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { visitUrl } from '~/lib/utils/url_utility';
import createFlash from '~/flash';
import MRWidgetService from '../../services/mr_widget_service';
export default {
name: 'DeploymentStopButton',
components: {
LoadingButton,
Icon,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
isDeployInProgress: {
type: Boolean,
required: true,
},
stopUrl: {
type: String,
required: true,
},
},
data() {
return {
isStopping: false,
};
},
computed: {
deployInProgressTooltip() {
return this.isDeployInProgress
? __('Stopping this environment is currently not possible as a deployment is in progress')
: '';
},
},
methods: {
stopEnvironment() {
const msg = __('Are you sure you want to stop this environment?');
const isConfirmed = confirm(msg); // eslint-disable-line
if (isConfirmed) {
this.isStopping = true;
MRWidgetService.stopEnvironment(this.stopUrl)
.then(res => res.data)
.then(data => {
if (data.redirect_url) {
visitUrl(data.redirect_url);
}
this.isStopping = false;
})
.catch(() => {
createFlash(
__('Something went wrong while stopping this environment. Please try again.'),
);
this.isStopping = false;
});
}
},
},
};
</script>
<template>
<span v-gl-tooltip :title="deployInProgressTooltip" class="d-inline-block" tabindex="0">
<loading-button
v-gl-tooltip
:loading="isStopping"
:disabled="isDeployInProgress"
:title="__('Stop environment')"
container-class="js-stop-env btn btn-default btn-sm inline prepend-left-4"
@click="stopEnvironment"
>
<icon name="stop" />
</loading-button>
</span>
</template>
<script>
import FilteredSearchDropdown from '~/vue_shared/components/filtered_search_dropdown.vue';
import ReviewAppLink from '../review_app_link.vue';
export default {
name: 'DeploymentViewButton',
components: {
FilteredSearchDropdown,
ReviewAppLink,
VisualReviewAppLink: () =>
import('ee_component/vue_merge_request_widget/components/visual_review_app_link.vue'),
},
props: {
deployment: {
type: Object,
required: true,
},
isCurrent: {
type: Boolean,
required: true,
},
showVisualReviewApp: {
type: Boolean,
required: false,
default: false,
},
visualReviewAppMeta: {
type: Object,
required: false,
default: () => ({
sourceProjectId: '',
sourceProjectPath: '',
mergeRequestId: '',
appUrl: '',
}),
},
},
computed: {
deploymentExternalUrl() {
if (this.deployment.changes && this.deployment.changes.length === 1) {
return this.deployment.changes[0].external_url;
}
return this.deployment.external_url;
},
shouldRenderDropdown() {
return this.deployment.changes && this.deployment.changes.length > 1;
},
},
};
</script>
<template>
<span>
<filtered-search-dropdown
v-if="shouldRenderDropdown"
class="js-mr-wigdet-deployment-dropdown inline"
:items="deployment.changes"
:main-action-link="deploymentExternalUrl"
filter-key="path"
>
<template slot="mainAction" slot-scope="slotProps">
<review-app-link
:is-current="isCurrent"
:link="deploymentExternalUrl"
:css-class="`deploy-link js-deploy-url inline ${slotProps.className}`"
/>
</template>
<template slot="result" slot-scope="slotProps">
<a
:href="slotProps.result.external_url"
target="_blank"
rel="noopener noreferrer nofollow"
class="js-deploy-url-menu-item menu-item"
>
<strong class="str-truncated-100 append-bottom-0 d-block">
{{ slotProps.result.path }}
</strong>
<p class="text-secondary str-truncated-100 append-bottom-0 d-block">
{{ slotProps.result.external_url }}
</p>
</a>
</template>
</filtered-search-dropdown>
<template v-else>
<review-app-link
:is-current="isCurrent"
:link="deploymentExternalUrl"
css-class="js-deploy-url deploy-link btn btn-default btn-sm inline"
/>
</template>
<visual-review-app-link
v-if="showVisualReviewApp"
:link="deploymentExternalUrl"
:app-metadata="visualReviewAppMeta"
/>
</span>
</template>
<script> <script>
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import statusCodes from '../../lib/utils/http_status'; import statusCodes from '~/lib/utils/http_status';
import { bytesToMiB } from '../../lib/utils/number_utils'; import { bytesToMiB } from '~/lib/utils/number_utils';
import { backOff } from '../../lib/utils/common_utils'; import { backOff } from '~/lib/utils/common_utils';
import MemoryGraph from '../../vue_shared/components/memory_graph.vue'; import MemoryGraph from '~/vue_shared/components/memory_graph.vue';
import MRWidgetService from '../services/mr_widget_service'; import MRWidgetService from '../../services/mr_widget_service';
export default { export default {
name: 'MemoryUsage', name: 'MemoryUsage',
......
<script> <script>
import _ from 'underscore'; import _ from 'underscore';
import ArtifactsApp from './artifacts_list_app.vue'; import ArtifactsApp from './artifacts_list_app.vue';
import Deployment from './deployment.vue'; import Deployment from './deployment/deployment.vue';
import MrWidgetContainer from './mr_widget_container.vue'; import MrWidgetContainer from './mr_widget_container.vue';
import MrWidgetPipeline from './mr_widget_pipeline.vue'; import MrWidgetPipeline from './mr_widget_pipeline.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
......
<script> <script>
import { __ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue'; import Icon from '~/vue_shared/components/icon.vue';
export default { export default {
...@@ -6,15 +7,24 @@ export default { ...@@ -6,15 +7,24 @@ export default {
Icon, Icon,
}, },
props: { props: {
link: { cssClass: {
type: String, type: String,
required: true, required: true,
}, },
cssClass: { isCurrent: {
type: Boolean,
required: true,
},
link: {
type: String, type: String,
required: true, required: true,
}, },
}, },
computed: {
linkText() {
return this.isCurrent ? __('View app') : __('View previous app');
},
},
}; };
</script> </script>
<template> <template>
...@@ -26,6 +36,6 @@ export default { ...@@ -26,6 +36,6 @@ export default {
data-track-event="open_review_app" data-track-event="open_review_app"
data-track-label="review_app" data-track-label="review_app"
> >
{{ __('View app') }} <icon class="fgray" name="external-link" /> {{ linkText }} <icon class="fgray" name="external-link" />
</a> </a>
</template> </template>
...@@ -10,7 +10,7 @@ import createFlash from '../flash'; ...@@ -10,7 +10,7 @@ import createFlash from '../flash';
import WidgetHeader from './components/mr_widget_header.vue'; import WidgetHeader from './components/mr_widget_header.vue';
import WidgetMergeHelp from './components/mr_widget_merge_help.vue'; import WidgetMergeHelp from './components/mr_widget_merge_help.vue';
import MrWidgetPipelineContainer from './components/mr_widget_pipeline_container.vue'; import MrWidgetPipelineContainer from './components/mr_widget_pipeline_container.vue';
import Deployment from './components/deployment.vue'; import Deployment from './components/deployment/deployment.vue';
import WidgetRelatedLinks from './components/mr_widget_related_links.vue'; import WidgetRelatedLinks from './components/mr_widget_related_links.vue';
import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue'; import MrWidgetAlertMessage from './components/mr_widget_alert_message.vue';
import MergedState from './components/states/mr_widget_merged.vue'; import MergedState from './components/states/mr_widget_merged.vue';
......
---
title: Update information and button text for deployment footer
merge_request: 18918
author:
type: changed
...@@ -73,6 +73,7 @@ export default { ...@@ -73,6 +73,7 @@ export default {
<review-app-link <review-app-link
v-else-if="environment.external_url" v-else-if="environment.external_url"
:link="environment.external_url" :link="environment.external_url"
:is-current="true"
css-class="btn btn-default btn-sm" css-class="btn btn-default btn-sm"
/> />
</div> </div>
......
...@@ -25,6 +25,7 @@ exports[`Environment Header has a failed pipeline matches the snapshot 1`] = ` ...@@ -25,6 +25,7 @@ exports[`Environment Header has a failed pipeline matches the snapshot 1`] = `
<reviewapplink-stub <reviewapplink-stub
cssclass="btn btn-default btn-sm" cssclass="btn btn-default btn-sm"
iscurrent="true"
link="http://example.com" link="http://example.com"
/> />
</div> </div>
...@@ -55,6 +56,7 @@ exports[`Environment Header has errors matches the snapshot 1`] = ` ...@@ -55,6 +56,7 @@ exports[`Environment Header has errors matches the snapshot 1`] = `
<reviewapplink-stub <reviewapplink-stub
cssclass="btn btn-default btn-sm" cssclass="btn btn-default btn-sm"
iscurrent="true"
link="http://example.com" link="http://example.com"
/> />
</div> </div>
...@@ -85,6 +87,7 @@ exports[`Environment Header renders name and link to app matches the snapshot 1` ...@@ -85,6 +87,7 @@ exports[`Environment Header renders name and link to app matches the snapshot 1`
<reviewapplink-stub <reviewapplink-stub
cssclass="btn btn-default btn-sm" cssclass="btn btn-default btn-sm"
iscurrent="true"
link="http://example.com" link="http://example.com"
/> />
</div> </div>
......
...@@ -6,6 +6,7 @@ import MRWidgetStore from 'ee/vue_merge_request_widget/stores/mr_widget_store'; ...@@ -6,6 +6,7 @@ import MRWidgetStore from 'ee/vue_merge_request_widget/stores/mr_widget_store';
import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key'; import filterByKey from 'ee/vue_shared/security_reports/store/utils/filter_by_key';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'spec/test_constants'; import { TEST_HOST } from 'spec/test_constants';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import state from 'ee/vue_shared/security_reports/store/state'; import state from 'ee/vue_shared/security_reports/store/state';
import mockData, { import mockData, {
...@@ -861,6 +862,7 @@ describe('ee merge request widget options', () => { ...@@ -861,6 +862,7 @@ describe('ee merge request widget options', () => {
external_url_formatted: 'diplo.', external_url_formatted: 'diplo.',
deployed_at: '2017-03-22T22:44:42.258Z', deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm', deployed_at_formatted: 'Mar 22, 2017 10:44pm',
status: SUCCESS,
}; };
beforeEach(done => { beforeEach(done => {
......
...@@ -2942,6 +2942,9 @@ msgstr "" ...@@ -2942,6 +2942,9 @@ msgstr ""
msgid "Callback URL" msgid "Callback URL"
msgstr "" msgstr ""
msgid "Can deploy manually to"
msgstr ""
msgid "Can override approvers and approvals required per merge request" msgid "Can override approvers and approvals required per merge request"
msgstr "" msgstr ""
...@@ -2969,6 +2972,9 @@ msgstr "" ...@@ -2969,6 +2972,9 @@ msgstr ""
msgid "Cancel this job" msgid "Cancel this job"
msgstr "" msgstr ""
msgid "Canceled deploy to"
msgstr ""
msgid "Cancelling Preview" msgid "Cancelling Preview"
msgstr "" msgstr ""
...@@ -19551,6 +19557,9 @@ msgstr "" ...@@ -19551,6 +19557,9 @@ msgstr ""
msgid "View open merge request" msgid "View open merge request"
msgstr "" msgstr ""
msgid "View previous app"
msgstr ""
msgid "View project labels" msgid "View project labels"
msgstr "" msgstr ""
......
...@@ -96,7 +96,7 @@ describe 'Merge request > User sees deployment widget', :js do ...@@ -96,7 +96,7 @@ describe 'Merge request > User sees deployment widget', :js do
visit project_merge_request_path(project, merge_request) visit project_merge_request_path(project, merge_request)
wait_for_requests wait_for_requests
expect(page).to have_content("Failed to deploy to #{environment.name}") expect(page).to have_content("Canceled deploy to #{environment.name}")
expect(page).not_to have_css('.js-deploy-time') expect(page).not_to have_css('.js-deploy-time')
end end
end end
......
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
const deploymentMockData = {
id: 15,
name: 'review/diplo',
url: '/root/review-apps/environments/15',
stop_url: '/root/review-apps/environments/15/stop',
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
external_url: 'http://gitlab.com.',
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
details: {},
status: SUCCESS,
changes: [
{
path: 'index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
},
{
path: 'imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
},
{
path: 'about/',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
export default deploymentMockData;
import { mount } from '@vue/test-utils';
import DeploymentComponent from '~/vue_merge_request_widget/components/deployment/deployment.vue';
import DeploymentInfo from '~/vue_merge_request_widget/components/deployment/deployment_info.vue';
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import DeploymentStopButton from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
import {
CREATED,
RUNNING,
SUCCESS,
FAILED,
CANCELED,
} from '~/vue_merge_request_widget/components/deployment/constants';
import deploymentMockData from './deployment_mock_data';
const deployDetail = {
playable_build: {
retry_path: '/root/test-deployments/-/jobs/1131/retry',
play_path: '/root/test-deployments/-/jobs/1131/play',
},
isManual: true,
};
describe('Deployment component', () => {
let wrapper;
const factory = (options = {}) => {
// This destroys any wrappers created before a nested call to factory reassigns it
if (wrapper && wrapper.destroy) {
wrapper.destroy();
}
wrapper = mount(DeploymentComponent, {
...options,
});
};
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
showMetrics: false,
},
});
});
afterEach(() => {
wrapper.destroy();
});
it('always renders DeploymentInfo', () => {
expect(wrapper.find(DeploymentInfo).exists()).toBe(true);
});
describe('status message and buttons', () => {
const noActions = [];
const noDetails = { isManual: false };
const deployGroup = [DeploymentViewButton, DeploymentStopButton];
describe.each`
status | previous | deploymentDetails | text | actionButtons
${CREATED} | ${true} | ${deployDetail} | ${'Can deploy manually to'} | ${deployGroup}
${CREATED} | ${true} | ${noDetails} | ${'Will deploy to'} | ${deployGroup}
${CREATED} | ${false} | ${deployDetail} | ${'Can deploy manually to'} | ${noActions}
${CREATED} | ${false} | ${noDetails} | ${'Will deploy to'} | ${noActions}
${RUNNING} | ${true} | ${deployDetail} | ${'Deploying to'} | ${deployGroup}
${RUNNING} | ${true} | ${noDetails} | ${'Deploying to'} | ${deployGroup}
${RUNNING} | ${false} | ${deployDetail} | ${'Deploying to'} | ${noActions}
${RUNNING} | ${false} | ${noDetails} | ${'Deploying to'} | ${noActions}
${SUCCESS} | ${true} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
${SUCCESS} | ${true} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
${SUCCESS} | ${false} | ${deployDetail} | ${'Deployed to'} | ${deployGroup}
${SUCCESS} | ${false} | ${noDetails} | ${'Deployed to'} | ${deployGroup}
${FAILED} | ${true} | ${deployDetail} | ${'Failed to deploy to'} | ${deployGroup}
${FAILED} | ${true} | ${noDetails} | ${'Failed to deploy to'} | ${deployGroup}
${FAILED} | ${false} | ${deployDetail} | ${'Failed to deploy to'} | ${noActions}
${FAILED} | ${false} | ${noDetails} | ${'Failed to deploy to'} | ${noActions}
${CANCELED} | ${true} | ${deployDetail} | ${'Canceled deploy to'} | ${deployGroup}
${CANCELED} | ${true} | ${noDetails} | ${'Canceled deploy to'} | ${deployGroup}
${CANCELED} | ${false} | ${deployDetail} | ${'Canceled deploy to'} | ${noActions}
${CANCELED} | ${false} | ${noDetails} | ${'Canceled deploy to'} | ${noActions}
`(
'$status + previous: $previous + manual: $deploymentDetails.isManual',
({ status, previous, deploymentDetails, text, actionButtons }) => {
beforeEach(() => {
const previousOrSuccess = Boolean(previous || status === SUCCESS);
const updatedDeploymentData = {
status,
deployed_at: previous ? deploymentMockData.deployed_at : null,
deployed_at_formatted: previous ? deploymentMockData.deployed_at_formatted : null,
external_url: previousOrSuccess ? deploymentMockData.external_url : null,
external_url_formatted: previousOrSuccess
? deploymentMockData.external_url_formatted
: null,
stop_url: previousOrSuccess ? deploymentMockData.stop_url : null,
details: deploymentDetails,
};
factory({
propsData: {
showMetrics: false,
deployment: {
...deploymentMockData,
...updatedDeploymentData,
},
},
});
});
it(`renders the text: ${text}`, () => {
expect(wrapper.find(DeploymentInfo).text()).toContain(text);
});
if (actionButtons.length > 0) {
describe('renders the expected button group', () => {
actionButtons.forEach(button => {
it(`renders ${button.name}`, () => {
expect(wrapper.find(button).exists()).toBe(true);
});
});
});
}
if (actionButtons.length === 0) {
describe('does not render the button group', () => {
[DeploymentViewButton, DeploymentStopButton].forEach(button => {
it(`does not render ${button.name}`, () => {
expect(wrapper.find(button).exists()).toBe(false);
});
});
});
}
if (actionButtons.includes(DeploymentViewButton)) {
it('renders the View button with expected text', () => {
if (status === SUCCESS) {
expect(wrapper.find(DeploymentViewButton).text()).toContain('View app');
} else {
expect(wrapper.find(DeploymentViewButton).text()).toContain('View previous app');
}
});
}
},
);
});
describe('hasExternalUrls', () => {
describe('when deployment has both external_url_formatted and external_url', () => {
it('should return true', () => {
expect(wrapper.vm.hasExternalUrls).toEqual(true);
});
it('should render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(true);
});
});
describe('when deployment has no external_url_formatted', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, external_url_formatted: null },
showMetrics: false,
},
});
});
it('should return false', () => {
expect(wrapper.vm.hasExternalUrls).toEqual(false);
});
it('should not render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
});
});
describe('when deployment has no external_url', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, external_url: null },
showMetrics: false,
},
});
});
it('should return false', () => {
expect(wrapper.vm.hasExternalUrls).toEqual(false);
});
it('should not render the View Button', () => {
expect(wrapper.find(DeploymentViewButton).exists()).toBe(false);
});
});
});
});
import { mount, createLocalVue } from '@vue/test-utils';
import DeploymentViewButton from '~/vue_merge_request_widget/components/deployment/deployment_view_button.vue';
import ReviewAppLink from '~/vue_merge_request_widget/components/review_app_link.vue';
import deploymentMockData from './deployment_mock_data';
describe('Deployment View App button', () => {
let wrapper;
const factory = (options = {}) => {
const localVue = createLocalVue();
wrapper = mount(localVue.extend(DeploymentViewButton), {
localVue,
...options,
});
};
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
isCurrent: true,
},
});
});
afterEach(() => {
wrapper.destroy();
});
describe('text', () => {
describe('when app is current', () => {
it('shows View app', () => {
expect(wrapper.find(ReviewAppLink).text()).toContain('View app');
});
});
describe('when app is not current', () => {
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
isCurrent: false,
},
});
});
it('shows View Previous app', () => {
expect(wrapper.find(ReviewAppLink).text()).toContain('View previous app');
});
});
});
describe('without changes', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, changes: null },
isCurrent: false,
},
});
});
it('renders the link to the review app without dropdown', () => {
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
});
});
describe('with a single change', () => {
beforeEach(() => {
factory({
propsData: {
deployment: { ...deploymentMockData, changes: [deploymentMockData.changes[0]] },
isCurrent: false,
},
});
});
it('renders the link to the review app without dropdown', () => {
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(false);
});
it('renders the link to the review app linked to to the first change', () => {
const expectedUrl = deploymentMockData.changes[0].external_url;
const deployUrl = wrapper.find('.js-deploy-url');
expect(deployUrl.attributes().href).not.toBeNull();
expect(deployUrl.attributes().href).toEqual(expectedUrl);
});
});
describe('with multiple changes', () => {
beforeEach(() => {
factory({
propsData: {
deployment: deploymentMockData,
isCurrent: false,
},
});
});
it('renders the link to the review app with dropdown', () => {
expect(wrapper.find('.js-mr-wigdet-deployment-dropdown').exists()).toBe(true);
});
it('renders all the links to the review apps', () => {
const allUrls = wrapper.findAll('.js-deploy-url-menu-item').wrappers;
const expectedUrls = deploymentMockData.changes.map(change => change.external_url);
expectedUrls.forEach((expectedUrl, idx) => {
const deployUrl = allUrls[idx];
expect(deployUrl.attributes().href).not.toBeNull();
expect(deployUrl.attributes().href).toEqual(expectedUrl);
});
});
});
});
import Vue from 'vue';
import deploymentStopComponent from '~/vue_merge_request_widget/components/deployment/deployment_stop_button.vue';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Deployment component', () => {
const Component = Vue.extend(deploymentStopComponent);
let deploymentMockData;
beforeEach(() => {
deploymentMockData = {
id: 15,
name: 'review/diplo',
url: '/root/review-apps/environments/15',
stop_url: '/root/review-apps/environments/15/stop',
metrics_url: '/root/review-apps/environments/15/deployments/1/metrics',
metrics_monitoring_url: '/root/review-apps/environments/15/metrics',
external_url: 'http://gitlab.com.',
external_url_formatted: 'gitlab',
deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm',
deployment_manual_actions: [],
status: SUCCESS,
changes: [
{
path: 'index.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
},
{
path: 'imgs/gallery.html',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
},
{
path: 'about/',
external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
},
],
};
});
let vm;
afterEach(() => {
vm.$destroy();
});
describe('', () => {
beforeEach(() => {
vm = mountComponent(Component, {
stopUrl: deploymentMockData.stop_url,
isDeployInProgress: false,
});
});
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));
const visitUrl = spyOnDependency(deploymentStopComponent, 'visitUrl').and.returnValue(true);
vm = mockStopEnvironment();
expect(window.confirm).toHaveBeenCalled();
expect(MRWidgetService.stopEnvironment).toHaveBeenCalledWith(deploymentMockData.stop_url);
setTimeout(() => {
expect(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();
});
});
});
});
import Vue from 'vue'; import Vue from 'vue';
import MemoryUsage from '~/vue_merge_request_widget/components/memory_usage.vue'; import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
const url = '/root/acets-review-apps/environments/15/deployments/1/metrics'; const url = '/root/acets-review-apps/environments/15/deployments/1/metrics';
......
...@@ -8,6 +8,7 @@ describe('review app link', () => { ...@@ -8,6 +8,7 @@ describe('review app link', () => {
const props = { const props = {
link: '/review', link: '/review',
cssClass: 'js-link', cssClass: 'js-link',
isCurrent: true,
}; };
let vm; let vm;
let el; let el;
......
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
export default { export default {
id: 132, id: 132,
iid: 22, iid: 22,
...@@ -290,15 +292,20 @@ export const mockStore = { ...@@ -290,15 +292,20 @@ export const mockStore = {
name: 'bogus', name: 'bogus',
external_url: 'https://fake.com', external_url: 'https://fake.com',
external_url_formatted: 'https://fake.com', external_url_formatted: 'https://fake.com',
status: SUCCESS,
}, },
{ {
id: 1, id: 1,
name: 'bogus-docs', name: 'bogus-docs',
external_url: 'https://fake.com', external_url: 'https://fake.com',
external_url_formatted: 'https://fake.com', external_url_formatted: 'https://fake.com',
status: SUCCESS,
}, },
], ],
postMergeDeployments: [{ id: 0, name: 'prod' }, { id: 1, name: 'prod-docs' }], postMergeDeployments: [
{ id: 0, name: 'prod', status: SUCCESS },
{ id: 1, name: 'prod-docs', status: SUCCESS },
],
troubleshootingDocsPath: 'troubleshooting-docs-path', troubleshootingDocsPath: 'troubleshooting-docs-path',
ciStatus: 'ci-status', ciStatus: 'ci-status',
hasCI: true, hasCI: true,
......
...@@ -6,6 +6,7 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; ...@@ -6,6 +6,7 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import mockData from './mock_data'; import mockData from './mock_data';
import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data'; import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
const returnPromise = data => const returnPromise = data =>
new Promise(resolve => { new Promise(resolve => {
...@@ -277,7 +278,9 @@ describe('mrWidgetOptions', () => { ...@@ -277,7 +278,9 @@ describe('mrWidgetOptions', () => {
describe('fetchDeployments', () => { describe('fetchDeployments', () => {
it('should fetch deployments', done => { it('should fetch deployments', done => {
spyOn(vm.service, 'fetchDeployments').and.returnValue(returnPromise([{ id: 1 }])); spyOn(vm.service, 'fetchDeployments').and.returnValue(
returnPromise([{ id: 1, status: SUCCESS }]),
);
vm.fetchPreMergeDeployments(); vm.fetchPreMergeDeployments();
...@@ -554,7 +557,7 @@ describe('mrWidgetOptions', () => { ...@@ -554,7 +557,7 @@ describe('mrWidgetOptions', () => {
deployed_at: '2017-03-22T22:44:42.258Z', deployed_at: '2017-03-22T22:44:42.258Z',
deployed_at_formatted: 'Mar 22, 2017 10:44pm', deployed_at_formatted: 'Mar 22, 2017 10:44pm',
changes, changes,
status: 'success', status: SUCCESS,
}; };
beforeEach(done => { beforeEach(done => {
......
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