diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 12:08:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-08-19 12:08:42 +0300 |
commit | b76ae638462ab0f673e5915986070518dd3f9ad3 (patch) | |
tree | bdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/environments | |
parent | 434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff) |
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/environments')
15 files changed, 746 insertions, 63 deletions
diff --git a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue index 76ad74e04d0..4783b92942c 100644 --- a/app/assets/javascripts/environments/components/confirm_rollback_modal.vue +++ b/app/assets/javascripts/environments/components/confirm_rollback_modal.vue @@ -1,29 +1,46 @@ <script> -/* eslint-disable vue/no-v-html */ /** * Render modal to confirm rollback/redeploy. */ - -import { GlModal } from '@gitlab/ui'; +import { GlModal, GlSprintf, GlLink } from '@gitlab/ui'; import { escape } from 'lodash'; -import { s__, sprintf } from '~/locale'; +import csrf from '~/lib/utils/csrf'; +import { __, s__, sprintf } from '~/locale'; import eventHub from '../event_hub'; export default { name: 'ConfirmRollbackModal', - components: { GlModal, + GlSprintf, + GlLink, + }, + model: { + prop: 'visible', + event: 'change', }, - props: { environment: { type: Object, required: true, }, + visible: { + type: Boolean, + required: false, + default: false, + }, + hasMultipleCommits: { + type: Boolean, + required: false, + default: true, + }, + retryUrl: { + type: String, + required: false, + default: null, + }, }, - computed: { modalTitle() { const title = this.environment.isLastDeployment @@ -34,58 +51,47 @@ export default { name: escape(this.environment.name), }); }, - commitShortSha() { - const { last_deployment } = this.environment; - return this.commitData(last_deployment, 'short_id'); - }, - - commitUrl() { - const { last_deployment } = this.environment; - return this.commitData(last_deployment, 'commit_path'); - }, + if (this.hasMultipleCommits) { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'short_id'); + } - commitTitle() { - const { last_deployment } = this.environment; - return this.commitData(last_deployment, 'title'); + return this.environment.commitShortSha; }, + commitUrl() { + if (this.hasMultipleCommits) { + const { last_deployment } = this.environment; + return this.commitData(last_deployment, 'commit_path'); + } - modalText() { - const linkStart = `<a class="commit-sha mr-0" href="${escape(this.commitUrl)}">`; - const commitId = escape(this.commitShortSha); - const linkEnd = '</a>'; - const name = escape(this.name); - const body = this.environment.isLastDeployment - ? s__( - 'Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?', - ) - : s__( - 'Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?', - ); - return sprintf( - body, - { - commitId, - linkStart, - linkEnd, - name, - }, - false, - ); + return this.environment.commitUrl; }, - modalActionText() { return this.environment.isLastDeployment ? s__('Environments|Re-deploy') : s__('Environments|Rollback'); }, - }, + primaryProps() { + let attributes = [{ variant: 'danger' }]; + + if (this.retryUrl) { + attributes = [...attributes, { 'data-method': 'post' }, { href: this.retryUrl }]; + } + return { + text: this.modalActionText, + attributes, + }; + }, + }, methods: { + handleChange(event) { + this.$emit('change', event); + }, onOk() { eventHub.$emit('rollbackEnvironment', this.environment); }, - commitData(lastDeployment, key) { if (lastDeployment && lastDeployment.commit) { return lastDeployment.commit[key]; @@ -94,16 +100,51 @@ export default { return ''; }, }, + csrf, + cancelProps: { + text: __('Cancel'), + attributes: [{ variant: 'danger' }], + }, }; </script> <template> <gl-modal :title="modalTitle" + :visible="visible" + :action-cancel="$options.cancelProps" + :action-primary="primaryProps" modal-id="confirm-rollback-modal" - :ok-title="modalActionText" - ok-variant="danger" @ok="onOk" + @change="handleChange" > - <p v-html="modalText"></p> + <gl-sprintf + v-if="environment.isLastDeployment" + :message=" + s__( + 'Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?', + ) + " + > + <template #link> + <gl-link :href="commitUrl" target="_blank" class="commit-sha mr-0">{{ + commitShortSha + }}</gl-link> + </template> + </gl-sprintf> + <gl-sprintf + v-else + :message=" + s__( + 'Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?', + ) + " + > + <template #name>{{ environment.name }}</template> + <template #link> + <gl-link :href="commitUrl" target="_blank" class="commit-sha mr-0">{{ + commitShortSha + }}</gl-link> + </template> + </gl-sprintf> </gl-modal> </template> diff --git a/app/assets/javascripts/environments/components/edit_environment.vue b/app/assets/javascripts/environments/components/edit_environment.vue new file mode 100644 index 00000000000..1cd960d7cd6 --- /dev/null +++ b/app/assets/javascripts/environments/components/edit_environment.vue @@ -0,0 +1,58 @@ +<script> +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { visitUrl } from '~/lib/utils/url_utility'; +import EnvironmentForm from './environment_form.vue'; + +export default { + components: { + EnvironmentForm, + }, + inject: ['projectEnvironmentsPath', 'updateEnvironmentPath'], + props: { + environment: { + required: true, + type: Object, + }, + }, + data() { + return { + formEnvironment: { + name: this.environment.name, + externalUrl: this.environment.external_url, + }, + loading: false, + }; + }, + methods: { + onChange(environment) { + this.formEnvironment = environment; + }, + onSubmit() { + this.loading = true; + axios + .put(this.updateEnvironmentPath, { + id: this.environment.id, + name: this.formEnvironment.name, + external_url: this.formEnvironment.externalUrl, + }) + .then(({ data: { path } }) => visitUrl(path)) + .catch((error) => { + const message = error.response.data.message[0]; + createFlash({ message }); + this.loading = false; + }); + }, + }, +}; +</script> +<template> + <environment-form + :cancel-path="projectEnvironmentsPath" + :environment="formEnvironment" + :title="__('Edit environment')" + :loading="loading" + @change="onChange" + @submit="onSubmit" + /> +</template> diff --git a/app/assets/javascripts/environments/components/enable_review_app_modal.vue b/app/assets/javascripts/environments/components/enable_review_app_modal.vue index b0c0f83b88a..d770a2302e8 100644 --- a/app/assets/javascripts/environments/components/enable_review_app_modal.vue +++ b/app/assets/javascripts/environments/components/enable_review_app_modal.vue @@ -1,5 +1,6 @@ <script> import { GlLink, GlModal, GlSprintf } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; import { s__ } from '~/locale'; import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; @@ -25,6 +26,9 @@ export default { step3: s__( `EnableReviewApp|%{stepStart}Step 3%{stepEnd}. Add it to the project %{linkStart}gitlab-ci.yml%{linkEnd} file.`, ), + step4: s__( + `EnableReviewApp|%{stepStart}Step 4 (optional)%{stepEnd}. Enable Visual Reviews by following the %{linkStart}setup instructions%{linkEnd}.`, + ), }, modalInfo: { closeText: s__('EnableReviewApp|Close'), @@ -45,6 +49,9 @@ export default { except: - ${this.defaultBranchName}`; }, + visualReviewsDocs() { + return helpPagePath('ci/review_apps/index.md', { anchor: 'visual-reviews' }); + }, }, }; </script> @@ -103,5 +110,15 @@ export default { </template> </gl-sprintf> </p> + <p> + <gl-sprintf :message="$options.instructionText.step4"> + <template #step="{ content }"> + <strong>{{ content }}</strong> + </template> + <template #link="{ content }"> + <gl-link :href="visualReviewsDocs" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> </gl-modal> </template> diff --git a/app/assets/javascripts/environments/components/environment_form.vue b/app/assets/javascripts/environments/components/environment_form.vue new file mode 100644 index 00000000000..6db8fe24e72 --- /dev/null +++ b/app/assets/javascripts/environments/components/environment_form.vue @@ -0,0 +1,146 @@ +<script> +import { GlButton, GlForm, GlFormGroup, GlFormInput, GlLink, GlSprintf } from '@gitlab/ui'; +import { helpPagePath } from '~/helpers/help_page_helper'; +import { isAbsolute } from '~/lib/utils/url_utility'; +import { __ } from '~/locale'; + +export default { + components: { + GlButton, + GlForm, + GlFormGroup, + GlFormInput, + GlLink, + GlSprintf, + }, + props: { + environment: { + required: true, + type: Object, + }, + title: { + required: true, + type: String, + }, + cancelPath: { + required: true, + type: String, + }, + loading: { + required: false, + type: Boolean, + default: false, + }, + }, + i18n: { + header: __('Environments'), + helpMessage: __( + 'Environments allow you to track deployments of your application. %{linkStart}More information%{linkEnd}.', + ), + nameLabel: __('Name'), + nameFeedback: __('This field is required'), + urlLabel: __('External URL'), + urlFeedback: __('The URL should start with http:// or https://'), + save: __('Save'), + cancel: __('Cancel'), + }, + helpPagePath: helpPagePath('ci/environments/index.md'), + data() { + return { + visited: { + name: null, + url: null, + }, + }; + }, + computed: { + valid() { + return { + name: this.visited.name && this.environment.name !== '', + url: this.visited.url && isAbsolute(this.environment.externalUrl), + }; + }, + }, + methods: { + onChange(env) { + this.$emit('change', env); + }, + visit(field) { + this.visited[field] = true; + }, + }, +}; +</script> +<template> + <div> + <h3 class="page-title"> + {{ title }} + </h3> + <hr /> + <div class="row gl-mt-3 gl-mb-3"> + <div class="col-lg-3"> + <h4 class="gl-mt-0"> + {{ $options.i18n.header }} + </h4> + <p> + <gl-sprintf :message="$options.i18n.helpMessage"> + <template #link="{ content }"> + <gl-link :href="$options.helpPagePath">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> + </div> + <gl-form + id="new_environment" + :aria-label="title" + class="col-lg-9" + @submit.prevent="$emit('submit')" + > + <gl-form-group + :label="$options.i18n.nameLabel" + label-for="environment_name" + :state="valid.name" + :invalid-feedback="$options.i18n.nameFeedback" + > + <gl-form-input + id="environment_name" + :value="environment.name" + :state="valid.name" + name="environment[name]" + required + @input="onChange({ ...environment, name: $event })" + @blur="visit('name')" + /> + </gl-form-group> + <gl-form-group + :label="$options.i18n.urlLabel" + :state="valid.url" + :invalid-feedback="$options.i18n.urlFeedback" + label-for="environment_external_url" + > + <gl-form-input + id="environment_external_url" + :value="environment.externalUrl" + :state="valid.url" + name="environment[external_url]" + type="url" + @input="onChange({ ...environment, externalUrl: $event })" + @blur="visit('url')" + /> + </gl-form-group> + + <div class="form-actions"> + <gl-button + :loading="loading" + type="submit" + variant="confirm" + name="commit" + class="js-no-auto-disable" + >{{ $options.i18n.save }}</gl-button + > + <gl-button :href="cancelPath">{{ $options.i18n.cancel }}</gl-button> + </div> + </gl-form> + </div> + </div> +</template> diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 5ae8b000fc0..897f6ce393e 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -776,23 +776,39 @@ export default { role="gridcell" > <div class="btn-group table-action-buttons" role="group"> - <pin-component v-if="canShowAutoStopDate" :auto-stop-url="autoStopUrl" /> + <pin-component + v-if="canShowAutoStopDate" + :auto-stop-url="autoStopUrl" + data-track-action="click_button" + data-track-label="environment_pin" + /> <external-url-component v-if="externalURL && canReadEnvironment" :external-url="externalURL" + data-track-action="click_button" + data-track-label="environment_url" /> <monitoring-button-component v-if="monitoringUrl && canReadEnvironment" :monitoring-url="monitoringUrl" + data-track-action="click_button" + data-track-label="environment_monitoring" /> - <actions-component v-if="actions.length > 0" :actions="actions" /> + <actions-component + v-if="actions.length > 0" + :actions="actions" + data-track-action="click_dropdown" + data-track-label="environment_actions" + /> <terminal-button-component v-if="model && model.terminal_path" :terminal-path="model.terminal_path" + data-track-action="click_button" + data-track-label="environment_terminal" /> <rollback-component @@ -800,11 +816,23 @@ export default { :environment="model" :is-last-deployment="isLastDeployment" :retry-url="retryUrl" + data-track-action="click_button" + data-track-label="environment_rollback" /> - <stop-component v-if="canStopEnvironment" :environment="model" /> + <stop-component + v-if="canStopEnvironment" + :environment="model" + data-track-action="click_button" + data-track-label="environment_stop" + /> - <delete-component v-if="canDeleteEnvironment" :environment="model" /> + <delete-component + v-if="canDeleteEnvironment" + :environment="model" + data-track-action="click_button" + data-track-label="environment_delete" + /> </div> </div> </div> diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue index e4cf5760987..105315dcf51 100644 --- a/app/assets/javascripts/environments/components/environments_app.vue +++ b/app/assets/javascripts/environments/components/environments_app.vue @@ -1,7 +1,9 @@ <script> -import { GlBadge, GlButton, GlModalDirective, GlTab, GlTabs } from '@gitlab/ui'; +import { GlBadge, GlButton, GlModalDirective, GlTab, GlTabs, GlAlert } from '@gitlab/ui'; import createFlash from '~/flash'; +import { setCookie, getCookie, parseBoolean } from '~/lib/utils/common_utils'; import { s__ } from '~/locale'; +import { ENVIRONMENTS_SURVEY_DISMISSED_COOKIE_NAME } from '../constants'; import eventHub from '../event_hub'; import environmentsMixin from '../mixins/environments_mixin'; import EnvironmentsPaginationApiMixin from '../mixins/environments_pagination_api_mixin'; @@ -15,6 +17,12 @@ export default { i18n: { newEnvironmentButtonLabel: s__('Environments|New environment'), reviewAppButtonLabel: s__('Environments|Enable review app'), + surveyAlertTitle: s__('Environments|Help us improve environments'), + surveyAlertText: s__( + 'Environments|Your feedback helps GitLab make environments better for you and other users. Participate and enter a sweepstake to win a USD 30 gift card.', + ), + surveyAlertButtonLabel: s__('Environments|Take the survey'), + surveyDismissButtonLabel: s__('Environments|Dismiss'), }, modal: { id: 'enable-review-app-info', @@ -25,6 +33,7 @@ export default { EnableReviewAppModal, GlBadge, GlButton, + GlAlert, GlTab, GlTabs, StopEnvironmentModal, @@ -56,6 +65,13 @@ export default { required: true, }, }, + data() { + return { + environmentsSurveyAlertDismissed: parseBoolean( + getCookie(ENVIRONMENTS_SURVEY_DISMISSED_COOKIE_NAME), + ), + }; + }, created() { eventHub.$on('toggleFolder', this.toggleFolder); @@ -105,6 +121,11 @@ export default { openFolders.forEach((folder) => this.fetchChildEnvironments(folder)); } }, + + onSurveyAlertDismiss() { + setCookie(ENVIRONMENTS_SURVEY_DISMISSED_COOKIE_NAME, 'true'); + this.environmentsSurveyAlertDismissed = true; + }, }, }; </script> @@ -135,6 +156,19 @@ export default { >{{ $options.i18n.newEnvironmentButtonLabel }}</gl-button > </div> + <gl-alert + v-if="!environmentsSurveyAlertDismissed" + class="gl-my-4" + :title="$options.i18n.surveyAlertTitle" + :primary-button-text="$options.i18n.surveyAlertButtonLabel" + variant="info" + dismissible + :dismiss-label="$options.i18n.surveyDismissButtonLabel" + primary-button-link="https://gitlab.fra1.qualtrics.com/jfe/form/SV_a2xyFsAA4D0w0Jg" + @dismiss="onSurveyAlertDismiss" + > + {{ $options.i18n.surveyAlertText }} + </gl-alert> <gl-tabs :value="activeTab" content-class="gl-display-none"> <gl-tab v-for="(tab, idx) in tabs" diff --git a/app/assets/javascripts/environments/components/environments_detail_header.vue b/app/assets/javascripts/environments/components/environments_detail_header.vue new file mode 100644 index 00000000000..467c89fd8b8 --- /dev/null +++ b/app/assets/javascripts/environments/components/environments_detail_header.vue @@ -0,0 +1,174 @@ +<script> +import { GlButton, GlModalDirective, GlTooltipDirective as GlTooltip, GlSprintf } from '@gitlab/ui'; +import csrf from '~/lib/utils/csrf'; +import { __, s__ } from '~/locale'; +import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue'; +import timeagoMixin from '~/vue_shared/mixins/timeago'; +import DeleteEnvironmentModal from './delete_environment_modal.vue'; +import StopEnvironmentModal from './stop_environment_modal.vue'; + +export default { + name: 'EnvironmentsDetailHeader', + csrf, + components: { + GlButton, + GlSprintf, + TimeAgo, + DeleteEnvironmentModal, + StopEnvironmentModal, + }, + directives: { + GlModalDirective, + GlTooltip, + }, + mixins: [timeagoMixin], + props: { + environment: { + type: Object, + required: true, + }, + canReadEnvironment: { + type: Boolean, + required: true, + }, + canAdminEnvironment: { + type: Boolean, + required: true, + }, + canUpdateEnvironment: { + type: Boolean, + required: true, + }, + canDestroyEnvironment: { + type: Boolean, + required: true, + }, + canStopEnvironment: { + type: Boolean, + required: true, + }, + cancelAutoStopPath: { + type: String, + required: false, + default: '', + }, + metricsPath: { + type: String, + required: false, + default: '', + }, + updatePath: { + type: String, + required: false, + default: '', + }, + terminalPath: { + type: String, + required: false, + default: '', + }, + }, + i18n: { + autoStopAtText: s__('Environments|Auto stops %{autoStopAt}'), + metricsButtonTitle: __('See metrics'), + metricsButtonText: __('Monitoring'), + editButtonText: __('Edit'), + stopButtonText: s__('Environments|Stop'), + deleteButtonText: s__('Environments|Delete'), + externalButtonTitle: s__('Environments|Open live environment'), + externalButtonText: __('View deployment'), + cancelAutoStopButtonTitle: __('Prevent environment from auto-stopping'), + }, + computed: { + shouldShowCancelAutoStopButton() { + return this.environment.isAvailable && Boolean(this.environment.autoStopAt); + }, + shouldShowExternalUrlButton() { + return this.canReadEnvironment && Boolean(this.environment.externalUrl); + }, + shouldShowStopButton() { + return this.canStopEnvironment && this.environment.isAvailable; + }, + shouldShowTerminalButton() { + return this.canAdminEnvironment && this.environment.hasTerminals; + }, + }, +}; +</script> +<template> + <header class="top-area gl-justify-content-between"> + <div class="gl-display-flex gl-flex-grow-1 gl-align-items-center"> + <h3 class="page-title"> + {{ environment.name }} + </h3> + <p v-if="shouldShowCancelAutoStopButton" class="gl-mb-0 gl-ml-3" data-testid="auto-stops-at"> + <gl-sprintf :message="$options.i18n.autoStopAtText"> + <template #autoStopAt> + <time-ago :time="environment.autoStopAt" /> + </template> + </gl-sprintf> + </p> + </div> + <div class="nav-controls gl-my-1"> + <form method="POST" :action="cancelAutoStopPath" data-testid="cancel-auto-stop-form"> + <input :value="$options.csrf.token" type="hidden" name="authenticity_token" /> + <gl-button + v-if="shouldShowCancelAutoStopButton" + v-gl-tooltip.hover + data-testid="cancel-auto-stop-button" + :title="$options.i18n.cancelAutoStopButtonTitle" + type="submit" + icon="thumbtack" + /> + </form> + <gl-button + v-if="shouldShowTerminalButton" + data-testid="terminal-button" + :href="terminalPath" + icon="terminal" + /> + <gl-button + v-if="shouldShowExternalUrlButton" + v-gl-tooltip.hover + data-testid="external-url-button" + :title="$options.i18n.externalButtonTitle" + :href="environment.externalUrl" + icon="external-link" + target="_blank" + >{{ $options.i18n.externalButtonText }}</gl-button + > + <gl-button + v-if="canReadEnvironment" + data-testid="metrics-button" + :href="metricsPath" + :title="$options.i18n.metricsButtonTitle" + icon="chart" + class="gl-mr-2" + > + {{ $options.i18n.metricsButtonText }} + </gl-button> + <gl-button v-if="canUpdateEnvironment" data-testid="edit-button" :href="updatePath"> + {{ $options.i18n.editButtonText }} + </gl-button> + <gl-button + v-if="shouldShowStopButton" + v-gl-modal-directive="'stop-environment-modal'" + data-testid="stop-button" + icon="stop" + variant="danger" + > + {{ $options.i18n.stopButtonText }} + </gl-button> + <gl-button + v-if="canDestroyEnvironment" + v-gl-modal-directive="'delete-environment-modal'" + data-testid="destroy-button" + variant="danger" + > + {{ $options.i18n.deleteButtonText }} + </gl-button> + </div> + <delete-environment-modal v-if="canDestroyEnvironment" :environment="environment" /> + <stop-environment-modal v-if="shouldShowStopButton" :environment="environment" /> + </header> +</template> diff --git a/app/assets/javascripts/environments/components/new_environment.vue b/app/assets/javascripts/environments/components/new_environment.vue new file mode 100644 index 00000000000..14da2668417 --- /dev/null +++ b/app/assets/javascripts/environments/components/new_environment.vue @@ -0,0 +1,51 @@ +<script> +import createFlash from '~/flash'; +import axios from '~/lib/utils/axios_utils'; +import { visitUrl } from '~/lib/utils/url_utility'; +import EnvironmentForm from './environment_form.vue'; + +export default { + components: { + EnvironmentForm, + }, + inject: ['projectEnvironmentsPath'], + data() { + return { + environment: { + name: '', + externalUrl: '', + }, + loading: false, + }; + }, + methods: { + onChange(env) { + this.environment = env; + }, + onSubmit() { + this.loading = true; + axios + .post(this.projectEnvironmentsPath, { + name: this.environment.name, + external_url: this.environment.externalUrl, + }) + .then(({ data: { path } }) => visitUrl(path)) + .catch((error) => { + const message = error.response.data.message[0]; + createFlash({ message }); + this.loading = false; + }); + }, + }, +}; +</script> +<template> + <environment-form + :cancel-path="projectEnvironmentsPath" + :environment="environment" + :title="__('New environment')" + :loading="loading" + @change="onChange($event)" + @submit="onSubmit" + /> +</template> diff --git a/app/assets/javascripts/environments/components/rollback_modal_manager.vue b/app/assets/javascripts/environments/components/rollback_modal_manager.vue new file mode 100644 index 00000000000..6aa7d96fdfd --- /dev/null +++ b/app/assets/javascripts/environments/components/rollback_modal_manager.vue @@ -0,0 +1,57 @@ +<script> +import { parseBoolean } from '~/lib/utils/common_utils'; +import ConfirmRollbackModal from './confirm_rollback_modal.vue'; + +export default { + components: { + ConfirmRollbackModal, + }, + props: { + selector: { + type: String, + required: true, + }, + }, + data() { + return { + environment: null, + retryPath: '', + visible: false, + }; + }, + mounted() { + document.querySelectorAll(this.selector).forEach((button) => { + button.addEventListener('click', (e) => { + e.preventDefault(); + const { + environmentName, + commitShortSha, + commitUrl, + isLastDeployment, + retryPath, + } = button.dataset; + + this.environment = { + name: environmentName, + commitShortSha, + commitUrl, + isLastDeployment: parseBoolean(isLastDeployment), + }; + this.retryPath = retryPath; + this.visible = true; + }); + }); + }, +}; +</script> + +<template> + <confirm-rollback-modal + v-if="environment" + v-model="visible" + :environment="environment" + :has-multiple-commits="false" + :retry-url="retryPath" + /> + <div v-else></div> +</template> diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index 6d427bef4e6..a02e72dfa72 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -38,3 +38,5 @@ export const CANARY_STATUS = { }; export const CANARY_UPDATE_MODAL = 'confirm-canary-change'; + +export const ENVIRONMENTS_SURVEY_DISMISSED_COOKIE_NAME = 'environments_survey_alert_dismissed'; diff --git a/app/assets/javascripts/environments/edit.js b/app/assets/javascripts/environments/edit.js new file mode 100644 index 00000000000..dd6680f64bd --- /dev/null +++ b/app/assets/javascripts/environments/edit.js @@ -0,0 +1,18 @@ +import Vue from 'vue'; +import EditEnvironment from './components/edit_environment.vue'; + +export default (el) => + new Vue({ + el, + provide: { + projectEnvironmentsPath: el.dataset.projectEnvironmentsPath, + updateEnvironmentPath: el.dataset.updateEnvironmentPath, + }, + render(h) { + return h(EditEnvironment, { + props: { + environment: JSON.parse(el.dataset.environment), + }, + }); + }, + }); diff --git a/app/assets/javascripts/environments/init_confirm_rollback_modal.js b/app/assets/javascripts/environments/init_confirm_rollback_modal.js new file mode 100644 index 00000000000..0161bb6078f --- /dev/null +++ b/app/assets/javascripts/environments/init_confirm_rollback_modal.js @@ -0,0 +1,16 @@ +import Vue from 'vue'; +import RollbackModalManager from './components/rollback_modal_manager.vue'; + +const mountConfirmRollbackModal = (optionalProps) => + new Vue({ + render(h) { + return h(RollbackModalManager, { + props: { + selector: '.js-confirm-rollback-modal-button', + ...optionalProps, + }, + }); + }, + }).$mount(); + +export default (optionalProps = {}) => mountConfirmRollbackModal(optionalProps); diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js index 6f701f87261..85cff73cc3e 100644 --- a/app/assets/javascripts/environments/mixins/environments_mixin.js +++ b/app/assets/javascripts/environments/mixins/environments_mixin.js @@ -108,7 +108,19 @@ export default { this.service .postAction(endpoint) - .then(() => this.fetchEnvironments()) + .then(() => { + // Originally, the detail page buttons were implemented as <form>s that POSTed + // to the server, which would naturally result in a page refresh. + // When environment details page was converted to Vue, the buttons were updated to trigger + // HTTP requests using `axios`, which did not cause a refresh on completion. + // To preserve the original behavior, we manually reload the page when + // network requests complete successfully. + if (!this.isDetailView) { + this.fetchEnvironments(); + } else { + window.location.reload(); + } + }) .catch((err) => { this.isLoading = false; createFlash({ diff --git a/app/assets/javascripts/environments/mount_show.js b/app/assets/javascripts/environments/mount_show.js index d0b68b0c14f..f1c2dfec94b 100644 --- a/app/assets/javascripts/environments/mount_show.js +++ b/app/assets/javascripts/environments/mount_show.js @@ -1,30 +1,48 @@ import Vue from 'vue'; -import DeleteEnvironmentModal from './components/delete_environment_modal.vue'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import EnvironmentsDetailHeader from './components/environments_detail_header.vue'; import environmentsMixin from './mixins/environments_mixin'; -export default () => { - const el = document.getElementById('delete-environment-modal'); +export const initHeader = () => { + const el = document.getElementById('environments-detail-view-header'); const container = document.getElementById('environments-detail-view'); + const dataset = convertObjectPropsToCamelCase(JSON.parse(container.dataset.details)); return new Vue({ el, - components: { - DeleteEnvironmentModal, - }, mixins: [environmentsMixin], data() { - const environment = JSON.parse(JSON.stringify(container.dataset)); - environment.delete_path = environment.deletePath; - environment.onSingleEnvironmentPage = true; + const environment = { + name: dataset.name, + id: Number(dataset.id), + externalUrl: dataset.externalUrl, + isAvailable: dataset.isEnvironmentAvailable, + hasTerminals: dataset.hasTerminals, + autoStopAt: dataset.autoStopAt, + onSingleEnvironmentPage: true, + // TODO: These two props are snake_case because the environments_mixin file uses + // them and the mixin is imported in several files. It would be nice to conver them to camelCase. + stop_path: dataset.environmentStopPath, + delete_path: dataset.environmentDeletePath, + }; return { environment, }; }, render(createElement) { - return createElement('delete-environment-modal', { + return createElement(EnvironmentsDetailHeader, { props: { environment: this.environment, + canDestroyEnvironment: dataset.canDestroyEnvironment, + canUpdateEnvironment: dataset.canUpdateEnvironment, + canReadEnvironment: dataset.canReadEnvironment, + canStopEnvironment: dataset.canStopEnvironment, + canAdminEnvironment: dataset.canAdminEnvironment, + cancelAutoStopPath: dataset.environmentCancelAutoStopPath, + terminalPath: dataset.environmentTerminalPath, + metricsPath: dataset.environmentMetricsPath, + updatePath: dataset.environmentEditPath, }, }); }, diff --git a/app/assets/javascripts/environments/new.js b/app/assets/javascripts/environments/new.js new file mode 100644 index 00000000000..76aaf809d17 --- /dev/null +++ b/app/assets/javascripts/environments/new.js @@ -0,0 +1,11 @@ +import Vue from 'vue'; +import NewEnvironment from './components/new_environment.vue'; + +export default (el) => + new Vue({ + el, + provide: { projectEnvironmentsPath: el.dataset.projectEnvironmentsPath }, + render(h) { + return h(NewEnvironment); + }, + }); |