diff options
Diffstat (limited to 'app/assets/javascripts')
6 files changed, 281 insertions, 131 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/constants.js b/app/assets/javascripts/vue_merge_request_widget/components/deployment/constants.js index 90741e3aa44..a7ab11290eb 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/constants.js +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/constants.js @@ -6,3 +6,8 @@ export const RUNNING = 'running'; export const SUCCESS = 'success'; export const FAILED = 'failed'; export const CANCELED = 'canceled'; + +// ACTION STATUSES +export const STOPPING = 'stopping'; +export const DEPLOYING = 'deploying'; +export const REDEPLOYING = 'redeploying'; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment.vue index 34866cdfa6f..6f77d2fa779 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment.vue @@ -1,18 +1,15 @@ <script> -import { __, s__ } from '~/locale'; +import DeploymentActions from './deployment_actions.vue'; 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'; +import { MANUAL_DEPLOY, WILL_DEPLOY, CREATED } 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: { + DeploymentActions, DeploymentInfo, - DeploymentStopButton, - DeploymentViewButton, }, props: { deployment: { @@ -40,38 +37,14 @@ export default { }, }, computed: { - appButtonText() { - return { - text: this.isCurrent ? s__('Review App|View app') : s__('Review App|View latest app'), - tooltip: this.isCurrent - ? '' - : __('View the latest successful deployment to this environment'), - }; - }, - 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); - }, - 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; + return Boolean(this.deployment.details?.playable_build?.play_path); }, }, }; @@ -87,22 +60,12 @@ export default { :deployment="deployment" :show-metrics="showMetrics" /> - <div> - <!-- show appropriate version of review app button --> - <deployment-view-button - v-if="hasExternalUrls" - :app-button-text="appButtonText" - :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> + <deployment-actions + :deployment="deployment" + :computed-deployment-status="computedDeploymentStatus" + :show-visual-review-app="showVisualReviewApp" + :visual-review-app-metadata="visualReviewAppMeta" + /> </div> </div> </div> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue new file mode 100644 index 00000000000..45798fbc9dc --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_action_button.vue @@ -0,0 +1,75 @@ +<script> +import { GlTooltipDirective, GlButton } from '@gitlab/ui'; +import { __ } from '~/locale'; +import { RUNNING } from './constants'; + +export default { + name: 'DeploymentActionButton', + components: { + GlButton, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + actionsConfiguration: { + type: Object, + required: true, + }, + actionInProgress: { + type: String, + required: false, + default: null, + }, + buttonTitle: { + type: String, + required: false, + default: '', + }, + computedDeploymentStatus: { + type: String, + required: true, + }, + containerClasses: { + type: String, + required: false, + default: '', + }, + }, + computed: { + isActionInProgress() { + return Boolean(this.computedDeploymentStatus === RUNNING || this.actionInProgress); + }, + actionInProgressTooltip() { + switch (this.actionInProgress) { + case this.actionsConfiguration.actionName: + return this.actionsConfiguration.busyText; + case null: + return ''; + default: + return __('Another action is currently in progress'); + } + }, + isLoading() { + return this.actionInProgress === this.actionsConfiguration.actionName; + }, + }, +}; +</script> + +<template> + <span v-gl-tooltip :title="actionInProgressTooltip" class="d-inline-block" tabindex="0"> + <gl-button + v-gl-tooltip + :title="buttonTitle" + :loading="isLoading" + :disabled="isActionInProgress" + :class="`btn btn-default btn-sm inline prepend-left-4 ${containerClasses}`" + @click="$emit('click')" + > + <span class="d-inline-flex align-items-baseline"> + <slot> </slot> + </span> + </gl-button> + </span> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue new file mode 100644 index 00000000000..a11e62b048a --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_actions.vue @@ -0,0 +1,190 @@ +<script> +import { GlIcon } from '@gitlab/ui'; +import { __, s__ } from '~/locale'; +import createFlash from '~/flash'; +import { visitUrl } from '~/lib/utils/url_utility'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import MRWidgetService from '../../services/mr_widget_service'; +import DeploymentActionButton from './deployment_action_button.vue'; +import DeploymentViewButton from './deployment_view_button.vue'; +import { MANUAL_DEPLOY, FAILED, SUCCESS, STOPPING, DEPLOYING, REDEPLOYING } from './constants'; + +export default { + name: 'DeploymentActions', + components: { + DeploymentActionButton, + DeploymentViewButton, + GlIcon, + }, + mixins: [glFeatureFlagsMixin()], + props: { + computedDeploymentStatus: { + type: String, + required: true, + }, + deployment: { + type: Object, + required: true, + }, + showVisualReviewApp: { + type: Boolean, + required: false, + default: false, + }, + visualReviewAppMeta: { + type: Object, + required: false, + default: () => ({ + sourceProjectId: '', + sourceProjectPath: '', + mergeRequestId: '', + appUrl: '', + }), + }, + }, + data() { + return { + actionInProgress: null, + constants: { + STOPPING, + DEPLOYING, + REDEPLOYING, + }, + }; + }, + computed: { + appButtonText() { + return { + text: this.isCurrent ? s__('Review App|View app') : s__('Review App|View latest app'), + tooltip: this.isCurrent + ? '' + : __('View the latest successful deployment to this environment'), + }; + }, + canBeManuallyDeployed() { + return this.computedDeploymentStatus === MANUAL_DEPLOY && Boolean(this.playPath); + }, + canBeManuallyRedeployed() { + return this.computedDeploymentStatus === FAILED && Boolean(this.redeployPath); + }, + shouldShowManualButtons() { + return this.glFeatures.deployFromFooter; + }, + hasExternalUrls() { + return Boolean(this.deployment.external_url && this.deployment.external_url_formatted); + }, + isCurrent() { + return this.computedDeploymentStatus === SUCCESS; + }, + playPath() { + return this.deployment.details?.playable_build?.play_path; + }, + redeployPath() { + return this.deployment.details?.playable_build?.retry_path; + }, + stopUrl() { + return this.deployment.stop_url; + }, + }, + actionsConfiguration: { + [STOPPING]: { + actionName: STOPPING, + buttonText: s__('MrDeploymentActions|Stop environment'), + busyText: __('This environment is being deployed'), + confirmMessage: __('Are you sure you want to stop this environment?'), + errorMessage: __('Something went wrong while stopping this environment. Please try again.'), + }, + [DEPLOYING]: { + actionName: DEPLOYING, + buttonText: s__('MrDeploymentActions|Deploy'), + busyText: __('This environment is being deployed'), + confirmMessage: __('Are you sure you want to deploy this environment?'), + errorMessage: __('Something went wrong while deploying this environment. Please try again.'), + }, + [REDEPLOYING]: { + actionName: REDEPLOYING, + buttonText: s__('MrDeploymentActions|Re-deploy'), + busyText: __('This environment is being re-deployed'), + confirmMessage: __('Are you sure you want to re-deploy this environment?'), + errorMessage: __('Something went wrong while deploying this environment. Please try again.'), + }, + }, + methods: { + executeAction(endpoint, { actionName, confirmMessage, errorMessage }) { + const isConfirmed = confirm(confirmMessage); //eslint-disable-line + + if (isConfirmed) { + this.actionInProgress = actionName; + + MRWidgetService.executeInlineAction(endpoint) + .then(resp => { + const redirectUrl = resp?.data?.redirect_url; + if (redirectUrl) { + visitUrl(redirectUrl); + } + }) + .catch(() => { + createFlash(errorMessage); + }) + .finally(() => { + this.actionInProgress = null; + }); + } + }, + stopEnvironment() { + this.executeAction(this.stopUrl, this.$options.actionsConfiguration[STOPPING]); + }, + deployManually() { + this.executeAction(this.playPath, this.$options.actionsConfiguration[DEPLOYING]); + }, + redeploy() { + this.executeAction(this.redeployPath, this.$options.actionsConfiguration[REDEPLOYING]); + }, + }, +}; +</script> + +<template> + <div> + <deployment-action-button + v-if="shouldShowManualButtons && canBeManuallyDeployed" + :action-in-progress="actionInProgress" + :actions-configuration="$options.actionsConfiguration[constants.DEPLOYING]" + :computed-deployment-status="computedDeploymentStatus" + container-classes="js-manual-deploy-action" + @click="deployManually" + > + <gl-icon name="play" /> + <span>{{ $options.actionsConfiguration[constants.DEPLOYING].buttonText }}</span> + </deployment-action-button> + <deployment-action-button + v-if="shouldShowManualButtons && canBeManuallyRedeployed" + :action-in-progress="actionInProgress" + :actions-configuration="$options.actionsConfiguration[constants.REDEPLOYING]" + :computed-deployment-status="computedDeploymentStatus" + container-classes="js-manual-redeploy-action" + @click="redeploy" + > + <gl-icon name="repeat" /> + <span>{{ $options.actionsConfiguration[constants.REDEPLOYING].buttonText }}</span> + </deployment-action-button> + <deployment-view-button + v-if="hasExternalUrls" + :app-button-text="appButtonText" + :deployment="deployment" + :show-visual-review-app="showVisualReviewApp" + :visual-review-app-metadata="visualReviewAppMeta" + /> + <deployment-action-button + v-if="stopUrl" + :action-in-progress="actionInProgress" + :computed-deployment-status="computedDeploymentStatus" + :actions-configuration="$options.actionsConfiguration[constants.STOPPING]" + :button-title="$options.actionsConfiguration[constants.STOPPING].buttonText" + container-classes="js-stop-env" + @click="stopEnvironment" + > + <gl-icon name="stop" /> + </deployment-action-button> + </div> +</template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_stop_button.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_stop_button.vue deleted file mode 100644 index e20296c41a2..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/deployment_stop_button.vue +++ /dev/null @@ -1,83 +0,0 @@ -<script> -import { GlTooltipDirective } from '@gitlab/ui'; -import { __ } from '~/locale'; -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> diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js index d22cb4ced80..c620023a6d6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js +++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js @@ -54,7 +54,7 @@ export default class MRWidgetService { return axios.post(this.endpoints.rebasePath); } - static stopEnvironment(url) { + static executeInlineAction(url) { return axios.post(url); } |