Commit 9a049aac authored by Andrew Fontaine's avatar Andrew Fontaine

Merge branch '254258-replace-bootstrap-modal' into 'master'

Migrate environment header to vue

See merge request gitlab-org/gitlab!64129
parents a44023b1 b4da2e19
<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>
...@@ -108,7 +108,19 @@ export default { ...@@ -108,7 +108,19 @@ export default {
this.service this.service
.postAction(endpoint) .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) => { .catch((err) => {
this.isLoading = false; this.isLoading = false;
createFlash({ createFlash({
......
import Vue from 'vue'; 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'; import environmentsMixin from './mixins/environments_mixin';
export default () => { export const initHeader = () => {
const el = document.getElementById('delete-environment-modal'); const el = document.getElementById('environments-detail-view-header');
const container = document.getElementById('environments-detail-view'); const container = document.getElementById('environments-detail-view');
const dataset = convertObjectPropsToCamelCase(JSON.parse(container.dataset.details));
return new Vue({ return new Vue({
el, el,
components: {
DeleteEnvironmentModal,
},
mixins: [environmentsMixin], mixins: [environmentsMixin],
data() { data() {
const environment = JSON.parse(JSON.stringify(container.dataset)); const environment = {
environment.delete_path = environment.deletePath; name: dataset.name,
environment.onSingleEnvironmentPage = true; 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 { return {
environment, environment,
}; };
}, },
render(createElement) { render(createElement) {
return createElement('delete-environment-modal', { return createElement(EnvironmentsDetailHeader, {
props: { props: {
environment: this.environment, 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,
}, },
}); });
}, },
......
import initShowEnvironment from '~/environments/mount_show'; import { initHeader } from '~/environments/mount_show';
initShowEnvironment(); initHeader();
...@@ -65,4 +65,31 @@ module EnvironmentHelper ...@@ -65,4 +65,31 @@ module EnvironmentHelper
content_tag(:span, text, class: klass) content_tag(:span, text, class: klass)
end end
end end
def environments_detail_data(user, project, environment)
{
name: environment.name,
id: environment.id,
external_url: environment.external_url,
can_update_environment: can?(current_user, :update_environment, environment),
can_destroy_environment: can_destroy_environment?(environment),
can_read_environment: can?(current_user, :read_environment, environment),
can_stop_environment: can?(current_user, :stop_environment, environment),
can_admin_environment: can?(current_user, :admin_environment, project),
environment_metrics_path: environment_metrics_path(environment),
environments_fetch_path: project_environments_path(project, format: :json),
environment_edit_path: edit_project_environment_path(project, environment),
environment_stop_path: stop_project_environment_path(project, environment),
environment_delete_path: environment_delete_path(environment),
environment_cancel_auto_stop_path: cancel_auto_stop_project_environment_path(project, environment),
environment_terminal_path: terminal_project_environment_path(project, environment),
has_terminals: environment.has_terminals?,
is_environment_available: environment.available?,
auto_stop_at: environment.auto_stop_at
}
end
def environments_detail_data_json(user, project, environment)
environments_detail_data(user, project, environment).to_json
end
end end
- if environment.external_url && can?(current_user, :read_environment, environment)
= link_to environment.external_url, target: '_blank', rel: 'noopener noreferrer', class: 'gl-button btn external-url has-tooltip qa-view-deployment', title: s_('Environments|Open live environment') do
= sprite_icon('external-link')
= _("View deployment")
- environment = local_assigns.fetch(:environment)
- return unless can?(current_user, :read_environment, environment)
= link_to environment_metrics_path(environment), title: _('See metrics'), class: 'gl-button btn metrics-button' do
= sprite_icon('chart', css_class: 'gl-mr-2')
= _("Monitoring")
- if environment.auto_stop_at? && environment.available?
= button_to cancel_auto_stop_project_environment_path(environment.project, environment), class: 'gl-button btn btn-secondary has-tooltip', title: _('Prevent environment from auto-stopping') do
= sprite_icon('thumbtack')
- if environment.has_terminals? && can?(current_user, :admin_environment, @project)
= link_to terminal_project_environment_path(@project, environment), class: 'gl-button btn terminal-button' do
= sprite_icon('terminal')
...@@ -5,58 +5,8 @@ ...@@ -5,58 +5,8 @@
- add_page_specific_style 'page_bundles/environments' - add_page_specific_style 'page_bundles/environments'
- add_page_specific_style 'page_bundles/ci_status' - add_page_specific_style 'page_bundles/ci_status'
#environments-detail-view{ data: { name: @environment.name, id: @environment.id, delete_path: environment_delete_path(@environment)} } #environments-detail-view{ data: { details: environments_detail_data_json(current_user, @project, @environment) } }
- if @environment.available? && can?(current_user, :stop_environment, @environment) #environments-detail-view-header
#stop-environment-modal.modal.fade{ tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
%h4.modal-title.d-flex.mw-100
= s_("Environments|Stopping")
%span.has-tooltip.text-truncate.ml-1.mr-1.flex-fill{ title: @environment.name, data: { container: '#stop-environment-modal' } }
#{@environment.name}?
.modal-body
%p= s_('Environments|Are you sure you want to stop this environment?')
- unless @environment.stop_action_available?
.warning_message
%p= s_('Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file.').html_safe % { emphasis_start: '<strong>'.html_safe,
emphasis_end: '</strong>'.html_safe,
ci_config_link_start: '<a href="https://docs.gitlab.com/ee/ci/yaml/" target="_blank" rel="noopener noreferrer">'.html_safe,
ci_config_link_end: '</a>'.html_safe }
%a{ href: 'https://docs.gitlab.com/ee/ci/environments/index.html#stopping-an-environment',
target: '_blank',
rel: 'noopener noreferrer' }
= s_('Environments|Learn more about stopping environments')
.modal-footer
= button_tag _('Cancel'), type: 'button', class: 'gl-button btn btn-cancel', data: { dismiss: 'modal' }
= button_to stop_project_environment_path(@project, @environment), class: 'gl-button btn btn-danger has-tooltip', method: :post do
= s_('Environments|Stop environment')
- if can_destroy_environment?(@environment)
#delete-environment-modal
.top-area.justify-content-between
.d-flex
%h3.page-title= @environment.name
- if @environment.auto_stop_at?
%p.align-self-end.gl-ml-3
= s_('Environments|Auto stops %{auto_stop_time}').html_safe % {auto_stop_time: time_ago_with_tooltip(@environment.auto_stop_at)}
.nav-controls.my-2
= render 'projects/environments/pin_button', environment: @environment
= render 'projects/environments/terminal_button', environment: @environment
= render 'projects/environments/external_url', environment: @environment
= render 'projects/environments/metrics_button', environment: @environment
- if can?(current_user, :update_environment, @environment)
= link_to _('Edit'), edit_project_environment_path(@project, @environment), class: 'btn'
- if @environment.available? && can?(current_user, :stop_environment, @environment)
= button_tag class: 'gl-button btn btn-danger', type: 'button', data: { toggle: 'modal',
target: '#stop-environment-modal' } do
= sprite_icon('stop')
= s_('Environments|Stop')
- if can_destroy_environment?(@environment)
= button_tag class: 'gl-button btn btn-danger', type: 'button', data: { toggle: 'modal',
target: '#delete-environment-modal' } do
= s_('Environments|Delete')
.environments-container .environments-container
- if @deployments.blank? - if @deployments.blank?
......
...@@ -12445,7 +12445,7 @@ msgstr "" ...@@ -12445,7 +12445,7 @@ msgstr ""
msgid "Environments|Auto stop in" msgid "Environments|Auto stop in"
msgstr "" msgstr ""
msgid "Environments|Auto stops %{auto_stop_time}" msgid "Environments|Auto stops %{autoStopAt}"
msgstr "" msgstr ""
msgid "Environments|Commit" msgid "Environments|Commit"
...@@ -12526,9 +12526,6 @@ msgstr "" ...@@ -12526,9 +12526,6 @@ msgstr ""
msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file." msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
msgstr "" msgstr ""
msgid "Environments|Note that this action will stop the environment, but it will %{emphasis_start}not%{emphasis_end} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ci_config_link_start}.gitlab-ci.yml%{ci_config_link_end} file."
msgstr ""
msgid "Environments|Open live environment" msgid "Environments|Open live environment"
msgstr "" msgstr ""
...@@ -12571,9 +12568,6 @@ msgstr "" ...@@ -12571,9 +12568,6 @@ msgstr ""
msgid "Environments|Stop environment" msgid "Environments|Stop environment"
msgstr "" msgstr ""
msgid "Environments|Stopping"
msgstr ""
msgid "Environments|Stopping %{environmentName}" msgid "Environments|Stopping %{environmentName}"
msgstr "" msgstr ""
......
...@@ -385,7 +385,6 @@ module QA ...@@ -385,7 +385,6 @@ module QA
module Deployments module Deployments
module Environments module Environments
autoload :Index, 'qa/page/project/deployments/environments/index' autoload :Index, 'qa/page/project/deployments/environments/index'
autoload :Show, 'qa/page/project/deployments/environments/show'
end end
end end
......
# frozen_string_literal: true
module QA
module Page
module Project
module Deployments
module Environments
class Show < Page::Base
view 'app/views/projects/environments/_external_url.html.haml' do
element :view_deployment
end
def view_deployment(&block)
new_window = window_opened_by { click_element(:view_deployment) }
within_window(new_window, &block) if block
end
end
end
end
end
end
end
...@@ -27,7 +27,7 @@ RSpec.describe 'Environment > Metrics' do ...@@ -27,7 +27,7 @@ RSpec.describe 'Environment > Metrics' do
shared_examples 'has environment selector' do shared_examples 'has environment selector' do
it 'has a working environment selector', :js do it 'has a working environment selector', :js do
click_link('See metrics') click_link 'Monitoring'
expect(page).to have_current_path(project_metrics_dashboard_path(project, environment: environment.id)) expect(page).to have_current_path(project_metrics_dashboard_path(project, environment: environment.id))
expect(page).to have_css('[data-qa-selector="environments_dropdown"]') expect(page).to have_css('[data-qa-selector="environments_dropdown"]')
...@@ -55,10 +55,10 @@ RSpec.describe 'Environment > Metrics' do ...@@ -55,10 +55,10 @@ RSpec.describe 'Environment > Metrics' do
create(:deployment, environment: environment, deployable: build) create(:deployment, environment: environment, deployable: build)
end end
it 'shows metrics' do it 'shows metrics', :js do
click_link('See metrics') click_link 'Monitoring'
expect(page).to have_css('div#prometheus-graphs') expect(page).to have_css('[data-qa-selector="prometheus_graphs"]')
end end
it_behaves_like 'has environment selector' it_behaves_like 'has environment selector'
......
...@@ -27,20 +27,6 @@ RSpec.describe 'Environment' do ...@@ -27,20 +27,6 @@ RSpec.describe 'Environment' do
visit_environment(environment) visit_environment(environment)
end end
it 'shows environment name' do
expect(page).to have_content(environment.name)
end
context 'without auto-stop' do
it 'does not show auto-stop text' do
expect(page).not_to have_content('Auto stops')
end
it 'does not show auto-stop button' do
expect(page).not_to have_selector(auto_stop_button_selector)
end
end
context 'with auto-stop' do context 'with auto-stop' do
let!(:environment) { create(:environment, :will_auto_stop, name: 'staging', project: project) } let!(:environment) { create(:environment, :will_auto_stop, name: 'staging', project: project) }
...@@ -48,11 +34,11 @@ RSpec.describe 'Environment' do ...@@ -48,11 +34,11 @@ RSpec.describe 'Environment' do
visit_environment(environment) visit_environment(environment)
end end
it 'shows auto stop info' do it 'shows auto stop info', :js do
expect(page).to have_content('Auto stops') expect(page).to have_content('Auto stops')
end end
it 'shows auto stop button' do it 'shows auto stop button', :js do
expect(page).to have_selector(auto_stop_button_selector) expect(page).to have_selector(auto_stop_button_selector)
expect(page.find(auto_stop_button_selector).find(:xpath, '..')['action']).to have_content(cancel_auto_stop_project_environment_path(environment.project, environment)) expect(page.find(auto_stop_button_selector).find(:xpath, '..')['action']).to have_content(cancel_auto_stop_project_environment_path(environment.project, environment))
end end
...@@ -80,7 +66,6 @@ RSpec.describe 'Environment' do ...@@ -80,7 +66,6 @@ RSpec.describe 'Environment' do
it 'does show deployment SHA' do it 'does show deployment SHA' do
expect(page).to have_link(deployment.short_sha) expect(page).to have_link(deployment.short_sha)
expect(page).not_to have_link('Re-deploy') expect(page).not_to have_link('Re-deploy')
expect(page).not_to have_terminal_button
end end
end end
...@@ -186,7 +171,7 @@ RSpec.describe 'Environment' do ...@@ -186,7 +171,7 @@ RSpec.describe 'Environment' do
let(:build) { create(:ci_build, pipeline: pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) }
let(:deployment) { create(:deployment, :success, environment: environment, deployable: build) } let(:deployment) { create(:deployment, :success, environment: environment, deployable: build) }
it 'does show an external link button' do it 'does show an external link button', :js do
expect(page).to have_link(nil, href: environment.external_url) expect(page).to have_link(nil, href: environment.external_url)
end end
end end
...@@ -200,10 +185,6 @@ RSpec.describe 'Environment' do ...@@ -200,10 +185,6 @@ RSpec.describe 'Environment' do
context 'for project maintainer' do context 'for project maintainer' do
let(:role) { :maintainer } let(:role) { :maintainer }
it 'shows the terminal button' do
expect(page).to have_terminal_button
end
context 'web terminal', :js do context 'web terminal', :js do
before do before do
# Stub #terminals as it causes js-enabled feature specs to # Stub #terminals as it causes js-enabled feature specs to
...@@ -224,14 +205,6 @@ RSpec.describe 'Environment' do ...@@ -224,14 +205,6 @@ RSpec.describe 'Environment' do
end end
end end
end end
context 'for developer' do
let(:role) { :developer }
it 'does not show terminal button' do
expect(page).not_to have_terminal_button
end
end
end end
end end
...@@ -259,7 +232,7 @@ RSpec.describe 'Environment' do ...@@ -259,7 +232,7 @@ RSpec.describe 'Environment' do
click_button('Stop') click_button('Stop')
click_button('Stop environment') # confirm modal click_button('Stop environment') # confirm modal
wait_for_all_requests wait_for_all_requests
expect(page).to have_content('close_app') expect(page).to have_button('Delete')
end end
end end
...@@ -269,7 +242,7 @@ RSpec.describe 'Environment' do ...@@ -269,7 +242,7 @@ RSpec.describe 'Environment' do
name: action.ref, project: project) name: action.ref, project: project)
end end
it 'does not allow to stop environment' do it 'does not allow to stop environment', :js do
expect(page).not_to have_button('Stop') expect(page).not_to have_button('Stop')
end end
end end
...@@ -277,7 +250,7 @@ RSpec.describe 'Environment' do ...@@ -277,7 +250,7 @@ RSpec.describe 'Environment' do
context 'for reporter' do context 'for reporter' do
let(:role) { :reporter } let(:role) { :reporter }
it 'does not show stop button' do it 'does not show stop button', :js do
expect(page).not_to have_button('Stop') expect(page).not_to have_button('Stop')
end end
end end
...@@ -287,7 +260,7 @@ RSpec.describe 'Environment' do ...@@ -287,7 +260,7 @@ RSpec.describe 'Environment' do
context 'when environment is stopped' do context 'when environment is stopped' do
let(:environment) { create(:environment, project: project, state: :stopped) } let(:environment) { create(:environment, project: project, state: :stopped) }
it 'does not show stop button' do it 'does not show stop button', :js do
expect(page).not_to have_button('Stop') expect(page).not_to have_button('Stop')
end end
end end
...@@ -323,7 +296,7 @@ RSpec.describe 'Environment' do ...@@ -323,7 +296,7 @@ RSpec.describe 'Environment' do
ref: 'feature') ref: 'feature')
end end
it 'user visits environment page' do it 'user visits environment page', :js do
visit_environment(environment) visit_environment(environment)
expect(page).to have_button('Stop') expect(page).to have_button('Stop')
...@@ -380,8 +353,4 @@ RSpec.describe 'Environment' do ...@@ -380,8 +353,4 @@ RSpec.describe 'Environment' do
def visit_environment(environment) def visit_environment(environment)
visit project_environment_path(environment.project, environment) visit project_environment_path(environment.project, environment)
end end
def have_terminal_button
have_link(nil, href: terminal_project_environment_path(project, environment))
end
end end
import { GlSprintf } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import DeleteEnvironmentModal from '~/environments/components/delete_environment_modal.vue';
import EnvironmentsDetailHeader from '~/environments/components/environments_detail_header.vue';
import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { createEnvironment } from './mock_data';
describe('Environments detail header component', () => {
const cancelAutoStopPath = '/my-environment/cancel/path';
const terminalPath = '/my-environment/terminal/path';
const metricsPath = '/my-environment/metrics/path';
const updatePath = '/my-environment/edit/path';
let wrapper;
const findHeader = () => wrapper.findByRole('heading');
const findAutoStopsAt = () => wrapper.findByTestId('auto-stops-at');
const findCancelAutoStopAtButton = () => wrapper.findByTestId('cancel-auto-stop-button');
const findCancelAutoStopAtForm = () => wrapper.findByTestId('cancel-auto-stop-form');
const findTerminalButton = () => wrapper.findByTestId('terminal-button');
const findExternalUrlButton = () => wrapper.findByTestId('external-url-button');
const findMetricsButton = () => wrapper.findByTestId('metrics-button');
const findEditButton = () => wrapper.findByTestId('edit-button');
const findStopButton = () => wrapper.findByTestId('stop-button');
const findDestroyButton = () => wrapper.findByTestId('destroy-button');
const findStopEnvironmentModal = () => wrapper.findComponent(StopEnvironmentModal);
const findDeleteEnvironmentModal = () => wrapper.findComponent(DeleteEnvironmentModal);
const buttons = [
['Cancel Auto Stop At', findCancelAutoStopAtButton],
['Terminal', findTerminalButton],
['External Url', findExternalUrlButton],
['Metrics', findMetricsButton],
['Edit', findEditButton],
['Stop', findStopButton],
['Destroy', findDestroyButton],
];
const createWrapper = ({ props }) => {
wrapper = shallowMountExtended(EnvironmentsDetailHeader, {
stubs: {
GlSprintf,
TimeAgo,
},
propsData: {
canReadEnvironment: false,
canAdminEnvironment: false,
canUpdateEnvironment: false,
canStopEnvironment: false,
canDestroyEnvironment: false,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
});
describe('default state with minimal access', () => {
beforeEach(() => {
createWrapper({ props: { environment: createEnvironment() } });
});
it('displays the environment name', () => {
expect(findHeader().text()).toBe('My environment');
});
it('does not display an auto stops at text', () => {
expect(findAutoStopsAt().exists()).toBe(false);
});
it.each(buttons)('does not display button: %s', (_, findSelector) => {
expect(findSelector().exists()).toBe(false);
});
it('does not display stop environment modal', () => {
expect(findStopEnvironmentModal().exists()).toBe(false);
});
it('does not display delete environment modal', () => {
expect(findDeleteEnvironmentModal().exists()).toBe(false);
});
});
describe('when auto stops at is enabled and environment is available', () => {
beforeEach(() => {
const now = new Date();
const tomorrow = new Date();
tomorrow.setDate(now.getDate() + 1);
createWrapper({
props: {
environment: createEnvironment({ autoStopAt: tomorrow.toISOString() }),
cancelAutoStopPath,
},
});
});
it('displays a text that describes when the environment is going to be stopped', () => {
expect(findAutoStopsAt().text()).toBe('Auto stops in 1 day');
});
it('displays a cancel auto stops at button with a form to make a post request', () => {
const button = findCancelAutoStopAtButton();
const form = findCancelAutoStopAtForm();
expect(form.attributes('action')).toBe(cancelAutoStopPath);
expect(form.attributes('method')).toBe('POST');
expect(button.props('icon')).toBe('thumbtack');
expect(button.attributes('type')).toBe('submit');
});
it('includes a csrf token', () => {
const input = findCancelAutoStopAtForm().find('input');
expect(input.attributes('name')).toBe('authenticity_token');
});
});
describe('when auto stops at is enabled and environment is unavailable (already stopped)', () => {
beforeEach(() => {
const now = new Date();
const tomorrow = new Date();
tomorrow.setDate(now.getDate() + 1);
createWrapper({
props: {
environment: createEnvironment({
autoStopAt: tomorrow.toISOString(),
isAvailable: false,
}),
cancelAutoStopPath,
},
});
});
it('does not display a text that describes when the environment is going to be stopped', () => {
expect(findAutoStopsAt().exists()).toBe(false);
});
it('displays a cancel auto stops at button with correct path', () => {
expect(findCancelAutoStopAtButton().exists()).toBe(false);
});
});
describe('when has a terminal', () => {
beforeEach(() => {
createWrapper({
props: {
environment: createEnvironment({ hasTerminals: true }),
canAdminEnvironment: true,
terminalPath,
},
});
});
it('displays the terminal button with correct path', () => {
expect(findTerminalButton().attributes('href')).toBe(terminalPath);
});
});
describe('when has an external url enabled', () => {
const externalUrl = 'https://example.com/my-environment/external/url';
beforeEach(() => {
createWrapper({
props: {
environment: createEnvironment({ hasTerminals: true, externalUrl }),
canReadEnvironment: true,
},
});
});
it('displays the external url button with correct path', () => {
expect(findExternalUrlButton().attributes('href')).toBe(externalUrl);
});
});
describe('when metrics are enabled', () => {
beforeEach(() => {
createWrapper({
props: {
environment: createEnvironment(),
canReadEnvironment: true,
metricsPath,
},
});
});
it('displays the metrics button with correct path', () => {
expect(findMetricsButton().attributes('href')).toBe(metricsPath);
});
});
describe('when has all admin rights', () => {
beforeEach(() => {
createWrapper({
props: {
environment: createEnvironment(),
canReadEnvironment: true,
canAdminEnvironment: true,
canStopEnvironment: true,
canUpdateEnvironment: true,
updatePath,
},
});
});
it('displays the edit button with correct path', () => {
expect(findEditButton().attributes('href')).toBe(updatePath);
});
it('displays the stop button with correct icon', () => {
expect(findStopButton().attributes('icon')).toBe('stop');
});
it('displays stop environment modal', () => {
expect(findStopEnvironmentModal().exists()).toBe(true);
});
});
describe('when the environment is unavailable and user has destroy permissions', () => {
beforeEach(() => {
createWrapper({
props: {
environment: createEnvironment({ isAvailable: false }),
canDestroyEnvironment: true,
},
});
});
it('displays a delete button', () => {
expect(findDestroyButton().exists()).toBe(true);
});
it('displays delete environment modal', () => {
expect(findDeleteEnvironmentModal().exists()).toBe(true);
});
});
});
...@@ -301,4 +301,22 @@ const tableData = { ...@@ -301,4 +301,22 @@ const tableData = {
}, },
}; };
export { environment, environmentsList, folder, serverData, tableData, deployBoardMockData }; const createEnvironment = (data = {}) => ({
id: 1,
name: 'My environment',
externalUrl: 'my external url',
isAvailable: true,
hasTerminals: false,
autoStopAt: null,
...data,
});
export {
environment,
environmentsList,
folder,
serverData,
tableData,
deployBoardMockData,
createEnvironment,
};
...@@ -22,4 +22,41 @@ RSpec.describe EnvironmentHelper do ...@@ -22,4 +22,41 @@ RSpec.describe EnvironmentHelper do
end end
end end
end end
describe '#environments_detail_data_json' do
subject { helper.environments_detail_data_json(user, project, environment) }
let_it_be(:auto_stop_at) { Time.now.utc }
let_it_be(:user) { create(:user) }
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:environment) { create(:environment, project: project, auto_stop_at: auto_stop_at) }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?).and_return(true)
end
it 'returns the correct data' do
expect(subject).to eq({
name: environment.name,
id: environment.id,
external_url: environment.external_url,
can_update_environment: true,
can_destroy_environment: true,
can_read_environment: true,
can_stop_environment: true,
can_admin_environment: true,
environment_metrics_path: environment_metrics_path(environment),
environments_fetch_path: project_environments_path(project, format: :json),
environment_edit_path: edit_project_environment_path(project, environment),
environment_stop_path: stop_project_environment_path(project, environment),
environment_delete_path: environment_delete_path(environment),
environment_cancel_auto_stop_path: cancel_auto_stop_project_environment_path(project, environment),
environment_terminal_path: terminal_project_environment_path(project, environment),
has_terminals: false,
is_environment_available: true,
auto_stop_at: auto_stop_at
}.to_json)
end
end
end end
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