Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-08-19 12:08:42 +0300
commitb76ae638462ab0f673e5915986070518dd3f9ad3 (patch)
treebdab0533383b52873be0ec0eb4d3c66598ff8b91 /app/assets/javascripts/environments
parent434373eabe7b4be9593d18a585fb763f1e5f1a6f (diff)
Add latest changes from gitlab-org/gitlab@14-2-stable-eev14.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/environments')
-rw-r--r--app/assets/javascripts/environments/components/confirm_rollback_modal.vue135
-rw-r--r--app/assets/javascripts/environments/components/edit_environment.vue58
-rw-r--r--app/assets/javascripts/environments/components/enable_review_app_modal.vue17
-rw-r--r--app/assets/javascripts/environments/components/environment_form.vue146
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue36
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue36
-rw-r--r--app/assets/javascripts/environments/components/environments_detail_header.vue174
-rw-r--r--app/assets/javascripts/environments/components/new_environment.vue51
-rw-r--r--app/assets/javascripts/environments/components/rollback_modal_manager.vue57
-rw-r--r--app/assets/javascripts/environments/constants.js2
-rw-r--r--app/assets/javascripts/environments/edit.js18
-rw-r--r--app/assets/javascripts/environments/init_confirm_rollback_modal.js16
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js14
-rw-r--r--app/assets/javascripts/environments/mount_show.js38
-rw-r--r--app/assets/javascripts/environments/new.js11
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);
+ },
+ });