Merge remote-tracking branch 'origin/list-multiple-clusters' into cluster-page-with-list-clusters
This commit is contained in:
commit
f1cce0cba8
|
@ -150,8 +150,8 @@ export default class Clusters {
|
|||
}
|
||||
|
||||
toggle() {
|
||||
this.toggleButton.classList.toggle('checked');
|
||||
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('checked').toString());
|
||||
this.toggleButton.classList.toggle('is-checked');
|
||||
this.toggleInput.setAttribute('value', this.toggleButton.classList.contains('is-checked').toString());
|
||||
}
|
||||
|
||||
showToken() {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
import Flash from '../flash';
|
||||
import { s__ } from '../locale';
|
||||
import ClustersService from './services/clusters_service';
|
||||
/**
|
||||
* Toggles loading and disabled classes.
|
||||
* @param {HTMLElement} button
|
||||
*/
|
||||
const toggleLoadingButton = (button) => {
|
||||
if (button.getAttribute('disabled')) {
|
||||
button.removeAttribute('disabled');
|
||||
} else {
|
||||
button.setAttribute('disabled', true);
|
||||
}
|
||||
|
||||
button.classList.toggle('is-loading');
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles checked class for the given button
|
||||
* @param {HTMLElement} button
|
||||
*/
|
||||
const toggleValue = (button) => {
|
||||
button.classList.toggle('is-checked');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles toggle buttons in the cluster's table.
|
||||
*
|
||||
* When the user clicks the toggle button for each cluster, it:
|
||||
* - toggles the button
|
||||
* - shows a loding and disabled state
|
||||
* - Makes a put request to the given endpoint
|
||||
* Once we receive the response, either:
|
||||
* 1) Show updated status in case of successfull response
|
||||
* 2) Show initial status in case of failed response
|
||||
*/
|
||||
export default function setClusterTableToggles() {
|
||||
document.querySelectorAll('.js-toggle-cluster-list')
|
||||
.forEach(button => button.addEventListener('click', (e) => {
|
||||
const toggleButton = e.currentTarget;
|
||||
const value = toggleButton.classList.contains('checked').toString();
|
||||
const endpoint = toggleButton.getAttribute('data-endpoint');
|
||||
|
||||
toggleValue(toggleButton);
|
||||
toggleLoadingButton(toggleButton);
|
||||
|
||||
ClustersService.updateCluster(endpoint, { cluster: { enabled: value } })
|
||||
.then(() => {
|
||||
toggleLoadingButton(toggleButton);
|
||||
})
|
||||
.catch(() => {
|
||||
toggleLoadingButton(toggleButton);
|
||||
toggleValue(toggleButton);
|
||||
Flash(s__('ClusterIntegration|Something went wrong on our end.'));
|
||||
});
|
||||
}));
|
||||
}
|
|
@ -17,4 +17,8 @@ export default class ClusterService {
|
|||
installApplication(appId) {
|
||||
return axios.post(this.appInstallEndpointMap[appId]);
|
||||
}
|
||||
|
||||
static updateCluster(endpoint, data) {
|
||||
return axios.put(endpoint, data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -558,7 +558,15 @@ import ProjectVariables from './project_variables';
|
|||
import(/* webpackChunkName: "clusters" */ './clusters/clusters_bundle')
|
||||
.then(cluster => new cluster.default()) // eslint-disable-line new-cap
|
||||
.catch((err) => {
|
||||
Flash(s__('ClusterIntegration|Problem setting up the cluster JavaScript'));
|
||||
Flash(s__('ClusterIntegration|Problem setting up the cluster'));
|
||||
throw err;
|
||||
});
|
||||
break;
|
||||
case 'projects:clusters:index':
|
||||
import(/* webpackChunkName: "clusters_index" */ './clusters/clusters_index')
|
||||
.then(clusterIndex => clusterIndex.default())
|
||||
.catch((err) => {
|
||||
Flash(s__('ClusterIntegration|Problem setting up the clusters list'));
|
||||
throw err;
|
||||
});
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import projectFeatureToggle from './project_feature_toggle.vue';
|
||||
import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
disabledInput: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleFeature() {
|
||||
if (!this.disabledInput) this.$emit('change', !this.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="toggle-wrapper">
|
||||
<input
|
||||
v-if="name"
|
||||
type="hidden"
|
||||
:name="name"
|
||||
:value="value"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle"
|
||||
class="project-feature-toggle"
|
||||
data-enabled-text="Enabled"
|
||||
data-disabled-text="Disabled"
|
||||
:class="{ checked: value, disabled: disabledInput }"
|
||||
@click="toggleFeature"
|
||||
/>
|
||||
</label>
|
||||
</template>
|
|
@ -1,6 +1,6 @@
|
|||
<script>
|
||||
import projectFeatureSetting from './project_feature_setting.vue';
|
||||
import projectFeatureToggle from './project_feature_toggle.vue';
|
||||
import projectFeatureToggle from '../../../vue_shared/components/toggle_button.vue';
|
||||
import projectSettingRow from './project_setting_row.vue';
|
||||
import { visibilityOptions, visibilityLevelDescriptions } from '../constants';
|
||||
import { toggleHiddenClassBySelector } from '../external';
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
<script>
|
||||
import loadingIcon from './loading_icon.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
disabledInput: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
isLoading: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
enabledText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Enabled',
|
||||
},
|
||||
disabledText: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Disabled',
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
loadingIcon,
|
||||
},
|
||||
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'change',
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggleFeature() {
|
||||
if (!this.disabledInput) this.$emit('change', !this.value);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="toggle-wrapper">
|
||||
<input
|
||||
type="hidden"
|
||||
:name="name"
|
||||
:value="value"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Toggle"
|
||||
class="project-feature-toggle"
|
||||
:data-enabled-text="enabledText"
|
||||
:data-disabled-text="disabledText"
|
||||
:class="{
|
||||
'is-checked': value,
|
||||
'is-disabled': disabledInput,
|
||||
'is-loading': isLoading
|
||||
}"
|
||||
@click="toggleFeature"
|
||||
>
|
||||
<loadingIcon class="loading-icon" />
|
||||
</button>
|
||||
</label>
|
||||
</template>
|
|
@ -44,6 +44,7 @@
|
|||
@import "framework/tabs";
|
||||
@import "framework/timeline";
|
||||
@import "framework/tooltips";
|
||||
@import "framework/toggle";
|
||||
@import "framework/typography";
|
||||
@import "framework/zen";
|
||||
@import "framework/blank";
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* Toggle button
|
||||
*
|
||||
* @usage
|
||||
* ### Active and Inactive text should be provided as data attributes:
|
||||
* <button type="button" class="project-feature-toggle" data-enabled-text="Enabled" data-disabled-text="Disabled">
|
||||
* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
|
||||
* </button>
|
||||
|
||||
* ### Checked should have `is-checked` class
|
||||
* <button type="button" class="project-feature-toggle is-checked" data-enabled-text="Enabled" data-disabled-text="Disabled">
|
||||
* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
|
||||
* </button>
|
||||
|
||||
* ### Disabled should have `is-disabled` class
|
||||
* <button type="button" class="project-feature-toggle is-disabled" data-enabled-text="Enabled" data-disabled-text="Disabled" disabled="true">
|
||||
* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
|
||||
* </button>
|
||||
|
||||
* ### Loading should have `is-loading` and an icon with `loading-icon` class
|
||||
* <button type="button" class="project-feature-toggle is-loading" data-enabled-text="Enabled" data-disabled-text="Disabled">
|
||||
* <i class="fa fa-spinner fa-spin loading-icon"></i>
|
||||
* </button>
|
||||
*/
|
||||
.project-feature-toggle {
|
||||
position: relative;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background: $feature-toggle-color-disabled;
|
||||
border-radius: 12px;
|
||||
padding: 3px;
|
||||
transition: all .4s ease;
|
||||
|
||||
&::selection,
|
||||
&::before::selection,
|
||||
&::after::selection {
|
||||
background: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
color: $feature-toggle-text-color;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 25px;
|
||||
right: 5px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
animation: animate-disabled .2s ease-in;
|
||||
content: attr(data-disabled-text);
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: relative;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 22px;
|
||||
height: 18px;
|
||||
left: 0;
|
||||
border-radius: 9px;
|
||||
background: $feature-toggle-color;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
color: $white-light;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
}
|
||||
|
||||
&.is-loading {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
display: block;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.is-checked {
|
||||
background: $feature-toggle-color-enabled;
|
||||
|
||||
&::before {
|
||||
left: 5px;
|
||||
right: 25px;
|
||||
animation: animate-enabled .2s ease-in;
|
||||
content: attr(data-enabled-text);
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: calc(100% - 22px);
|
||||
}
|
||||
}
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-min) {
|
||||
width: 50px;
|
||||
|
||||
&::before,
|
||||
&.is-checked::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animate-enabled {
|
||||
0%, 35% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes animate-disabled {
|
||||
0%, 35% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
}
|
|
@ -14,3 +14,13 @@
|
|||
}
|
||||
|
||||
@include new-style-dropdown('.clusters-dropdown ');
|
||||
|
||||
.clusters-container {
|
||||
.nav-bar-right {
|
||||
padding: $gl-padding-top $gl-padding;
|
||||
}
|
||||
|
||||
.empty-state .svg-content img {
|
||||
width: 145px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,93 +126,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.project-feature-toggle {
|
||||
position: relative;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
background: $feature-toggle-color-disabled;
|
||||
border-radius: 12px;
|
||||
padding: 3px;
|
||||
transition: all .4s ease;
|
||||
|
||||
&::selection,
|
||||
&::before::selection,
|
||||
&::after::selection {
|
||||
background: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
color: $feature-toggle-text-color;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 25px;
|
||||
right: 5px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
animation: animate-disabled .2s ease-in;
|
||||
content: attr(data-disabled-text);
|
||||
}
|
||||
|
||||
&::after {
|
||||
position: relative;
|
||||
display: block;
|
||||
content: "";
|
||||
width: 22px;
|
||||
height: 18px;
|
||||
left: 0;
|
||||
border-radius: 9px;
|
||||
background: $feature-toggle-color;
|
||||
transition: all .2s ease;
|
||||
}
|
||||
|
||||
&.checked {
|
||||
background: $feature-toggle-color-enabled;
|
||||
|
||||
&::before {
|
||||
left: 5px;
|
||||
right: 25px;
|
||||
animation: animate-enabled .2s ease-in;
|
||||
content: attr(data-enabled-text);
|
||||
}
|
||||
|
||||
&::after {
|
||||
left: calc(100% - 22px);
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@media (max-width: $screen-xs-min) {
|
||||
width: 50px;
|
||||
|
||||
&::before,
|
||||
&.checked::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes animate-enabled {
|
||||
0%, 35% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes animate-disabled {
|
||||
0%, 35% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
}
|
||||
|
||||
.project-home-panel,
|
||||
.group-home-panel {
|
||||
padding-top: 24px;
|
||||
|
|
|
@ -8,11 +8,12 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
STATUS_POLLING_INTERVAL = 10_000
|
||||
|
||||
def index
|
||||
if project.cluster
|
||||
redirect_to project_cluster_path(project, project.cluster)
|
||||
else
|
||||
redirect_to new_project_cluster_path(project)
|
||||
end
|
||||
@scope = params[:scope] || 'all'
|
||||
clusters = ClustersFinder.new(project, current_user, @scope).execute
|
||||
@clusters = clusters.page(params[:page])
|
||||
@active_count = project.clusters.enabled.count
|
||||
@inactive_count = project.clusters.disabled.count
|
||||
@all_count = @active_count + @inactive_count
|
||||
end
|
||||
|
||||
def new
|
||||
|
@ -39,10 +40,20 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
.execute(cluster)
|
||||
|
||||
if cluster.valid?
|
||||
flash[:notice] = "Cluster was successfully updated."
|
||||
redirect_to project_cluster_path(project, project.cluster)
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
head :no_content
|
||||
end
|
||||
format.html do
|
||||
flash[:notice] = "Cluster was successfully updated."
|
||||
redirect_to project_cluster_path(project, project.cluster)
|
||||
end
|
||||
end
|
||||
else
|
||||
render :show
|
||||
respond_to do |format|
|
||||
format.json { head :bad_request }
|
||||
format.html { render :show }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -59,7 +70,20 @@ class Projects::ClustersController < Projects::ApplicationController
|
|||
private
|
||||
|
||||
def cluster
|
||||
@cluster ||= project.clusters.find(params[:id]).present(current_user: current_user) || render_404
|
||||
@cluster ||= project.clusters.find_by(id: params[:id])&.present(current_user: current_user) || render_404
|
||||
end
|
||||
|
||||
def create_params
|
||||
params.require(:cluster).permit(
|
||||
:enabled,
|
||||
:name,
|
||||
:provider_type,
|
||||
provider_gcp_attributes: [
|
||||
:gcp_project_id,
|
||||
:zone,
|
||||
:num_nodes,
|
||||
:machine_type
|
||||
])
|
||||
end
|
||||
|
||||
def update_params
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
class ClustersFinder
|
||||
def initialize(project, user, scope)
|
||||
@project = project
|
||||
@user = user
|
||||
@scope = scope || :active
|
||||
end
|
||||
|
||||
def execute
|
||||
clusters = project.clusters
|
||||
filter_by_scope(clusters)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :project, :user, :scope
|
||||
|
||||
def filter_by_scope(clusters)
|
||||
case scope.to_sym
|
||||
when :all
|
||||
clusters
|
||||
when :inactive
|
||||
clusters.disabled
|
||||
when :active
|
||||
clusters.enabled
|
||||
else
|
||||
raise "Invalid scope #{scope}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
module ClustersHelper
|
||||
def can_toggle_cluster?(cluster)
|
||||
can?(current_user, :update_cluster, cluster) && cluster.created?
|
||||
end
|
||||
end
|
|
@ -55,6 +55,10 @@ module Clusters
|
|||
end
|
||||
end
|
||||
|
||||
def created?
|
||||
status_name == :created
|
||||
end
|
||||
|
||||
def applications
|
||||
[
|
||||
application_helm || build_application_helm,
|
||||
|
|
|
@ -5,6 +5,8 @@ module Clusters
|
|||
def execute(access_token)
|
||||
@access_token = access_token
|
||||
|
||||
raise Exception.new('Instance does not support multiple clusters') unless can_create_cluster?
|
||||
|
||||
create_cluster.tap do |cluster|
|
||||
ClusterProvisionWorker.perform_async(cluster.id) if cluster.persisted?
|
||||
end
|
||||
|
@ -25,5 +27,9 @@ module Clusters
|
|||
|
||||
@cluster_params = params.merge(user: current_user, projects: [project])
|
||||
end
|
||||
|
||||
def can_create_cluster?
|
||||
return project.clusters.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -187,7 +187,7 @@
|
|||
= nav_link(controller: [:clusters, :user, :gcp]) do
|
||||
= link_to project_clusters_path(@project), title: 'Cluster', class: 'shortcuts-cluster' do
|
||||
%span
|
||||
Cluster
|
||||
Clusters
|
||||
|
||||
- if @project.feature_available?(:builds, current_user) && !@project.empty_repo?
|
||||
= nav_link(path: 'pipelines#charts') do
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
.gl-responsive-table-row
|
||||
.table-section.section-30
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Cluster")
|
||||
.table-mobile-content
|
||||
= link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster)
|
||||
.table-section.section-30
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment pattern")
|
||||
.table-mobile-content= cluster.environment_scope
|
||||
.table-section.section-30
|
||||
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace")
|
||||
.table-mobile-content= cluster.platform_kubernetes&.namespace
|
||||
.table-section.section-10
|
||||
.table-mobile-header{ role: "rowheader" }
|
||||
.table-mobile-content
|
||||
%button{ type: "button",
|
||||
class: "js-toggle-cluster-list project-feature-toggle #{'is-checked' if cluster.enabled?} #{'is-disabled' if !can_toggle_cluster?(cluster)}",
|
||||
"aria-label": s_("ClusterIntegration|Toggle Cluster"),
|
||||
disabled: !can_toggle_cluster?(cluster),
|
||||
data: { "enabled-text": s_("ClusterIntegration|Active"),
|
||||
"disabled-text": s_("ClusterIntegration|Inactive"),
|
||||
endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
|
||||
= icon("spinner spin", class: "loading-icon")
|
|
@ -0,0 +1,12 @@
|
|||
.row.empty-state
|
||||
.col-xs-12
|
||||
.svg-content= image_tag 'illustrations/clusters_empty.svg'
|
||||
.col-xs-12.text-center
|
||||
.text-content
|
||||
%h4= s_('ClusterIntegration|Integrate cluster automation')
|
||||
- link_to_help_page = link_to(s_('ClusterIntegration|Learn more about Clusters'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
|
||||
%p= s_('ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
|
||||
|
||||
%p
|
||||
= link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
|
||||
.fade-left= icon("angle-left")
|
||||
.fade-right= icon("angle-right")
|
||||
%ul.nav-links.scrolling-tabs
|
||||
%li{ class: ('active' if @scope == 'active') }>
|
||||
= link_to project_clusters_path(@project, scope: :active), class: "js-active-tab" do
|
||||
= s_("ClusterIntegration|Active")
|
||||
%span.badge= @active_count
|
||||
%li{ class: ('active' if @scope == 'inactive') }>
|
||||
= link_to project_clusters_path(@project, scope: :inactive), class: "js-inactive-tab" do
|
||||
= s_("ClusterIntegration|Inactive")
|
||||
%span.badge= @inactive_count
|
||||
%li{ class: ('active' if @scope.nil? || @scope == 'all') }>
|
||||
= link_to project_clusters_path(@project), class: "js-all-tab" do
|
||||
= s_("ClusterIntegration|All")
|
||||
%span.badge= @all_count
|
||||
.pull-right.nav-bar-right
|
||||
= link_to s_("ClusterIntegration|Add cluster"), new_project_cluster_path(@project), class: "btn btn-success disabled has-tooltip js-add-cluster", title: s_("ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate")
|
|
@ -0,0 +1,24 @@
|
|||
- breadcrumb_title "Clusters"
|
||||
- page_title "Clusters"
|
||||
|
||||
.clusters-container
|
||||
- if !@clusters.empty?
|
||||
= render "tabs"
|
||||
.ci-table.js-clusters-list
|
||||
.gl-responsive-table-row.table-row-header{ role: "row" }
|
||||
.table-section.section-30{ role: "rowheader" }
|
||||
= s_("ClusterIntegration|Cluster")
|
||||
.table-section.section-30{ role: "rowheader" }
|
||||
= s_("ClusterIntegration|Environment pattern")
|
||||
.table-section.section-30{ role: "rowheader" }
|
||||
= s_("ClusterIntegration|Project namespace")
|
||||
.table-section.section-10{ role: "rowheader" }
|
||||
- @clusters.each do |cluster|
|
||||
= render "cluster", cluster: cluster
|
||||
= paginate @clusters, theme: "gitlab"
|
||||
- elsif @scope == 'all'
|
||||
= render "empty_state"
|
||||
- else
|
||||
= render "tabs"
|
||||
.prepend-top-20.text-center
|
||||
= s_("ClusterIntegration|There are no clusters to show")
|
|
@ -1,5 +1,6 @@
|
|||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
- breadcrumb_title "Cluster"
|
||||
- add_to_breadcrumbs "Clusters", project_clusters_path(@project)
|
||||
- breadcrumb_title @cluster.id
|
||||
- page_title _("Cluster")
|
||||
|
||||
- expanded = Rails.env.test?
|
||||
|
|
|
@ -8,8 +8,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: gitlab 1.0.0\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2017-10-22 16:40+0300\n"
|
||||
"PO-Revision-Date: 2017-10-22 16:40+0300\n"
|
||||
"POT-Creation-Date: 2017-12-02 15:22+0000\n"
|
||||
"PO-Revision-Date: 2017-12-02 15:22+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
"Language: \n"
|
||||
|
@ -36,6 +36,11 @@ msgstr[1] ""
|
|||
msgid "%{commit_author_link} committed %{commit_timeago}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{count} participant"
|
||||
msgid_plural "%{count} participants"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
|
||||
msgstr ""
|
||||
|
||||
|
@ -56,6 +61,12 @@ msgstr[1] ""
|
|||
msgid "(checkout the %{link} for information on how to install it)."
|
||||
msgstr ""
|
||||
|
||||
msgid "+ %{moreCount} more"
|
||||
msgstr ""
|
||||
|
||||
msgid "- show less"
|
||||
msgstr ""
|
||||
|
||||
msgid "1 pipeline"
|
||||
msgid_plural "%d pipelines"
|
||||
msgstr[0] ""
|
||||
|
@ -115,6 +126,12 @@ msgstr ""
|
|||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred when toggling the notification subscription"
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred while fetching sidebar data"
|
||||
msgstr ""
|
||||
|
||||
msgid "An error occurred. Please try again."
|
||||
msgstr ""
|
||||
|
||||
|
@ -184,6 +201,9 @@ msgstr ""
|
|||
msgid "AutoDevOps|You can activate %{link_to_settings} for this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Available"
|
||||
msgstr ""
|
||||
|
||||
msgid "Branch"
|
||||
msgid_plural "Branches"
|
||||
msgstr[0] ""
|
||||
|
@ -399,7 +419,34 @@ msgstr ""
|
|||
msgid "Cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below"
|
||||
msgid "ClusterIntegration|%{appList} was successfully installed on your cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|A cluster has been set up on this project through the Kubernetes integration page"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Active"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Add an existing cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Add cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|All"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Applications"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Choose how to set up cluster integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Cluster details"
|
||||
|
@ -420,24 +467,48 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Cluster is being created on Google Container Engine..."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Cluster management"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Cluster name"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Cluster was successfully created on Google Container Engine"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Copy cluster name"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Create a new cluster on Google Container Engine right from GitLab"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Create cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Create new cluster on Google Container Engine"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Create on GKE"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Enable cluster integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Environment pattern"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|GKE pricing"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|GitLab Runner"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Google Cloud Platform project ID"
|
||||
msgstr ""
|
||||
|
||||
|
@ -447,9 +518,36 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Google Container Engine project"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Helm Tiller"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Inactive"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Ingress"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Install"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Installed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Installing"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Integrate cluster automation"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Learn more about %{link_to_documentation}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Learn more about Clusters"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Machine type"
|
||||
msgstr ""
|
||||
|
||||
|
@ -459,16 +557,31 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Manage Cluster integration on your GitLab project"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Manage Kubernetes integration"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Note:"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Number of nodes"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Project namespace (optional, unique)"
|
||||
msgid "ClusterIntegration|Problem setting up the cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Problem setting up the clusters list"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Project namespace"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration."
|
||||
|
@ -483,6 +596,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Container Engine."
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Request to begin installing failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|See and edit the details for your cluster"
|
||||
msgstr ""
|
||||
|
||||
|
@ -501,6 +617,15 @@ msgstr ""
|
|||
msgid "ClusterIntegration|Something went wrong while creating your cluster on Google Container Engine"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Something went wrong while installing %{title}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|There are no clusters to show"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|Toggle Cluster"
|
||||
msgstr ""
|
||||
|
||||
|
@ -522,6 +647,9 @@ msgstr ""
|
|||
msgid "ClusterIntegration|help page"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|installing applications"
|
||||
msgstr ""
|
||||
|
||||
msgid "ClusterIntegration|meets the requirements"
|
||||
msgstr ""
|
||||
|
||||
|
@ -536,6 +664,11 @@ msgid_plural "Commits"
|
|||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Commit %d file"
|
||||
msgid_plural "Commit %d files"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
msgid "Commit Message"
|
||||
msgstr ""
|
||||
|
||||
|
@ -617,6 +750,15 @@ msgstr ""
|
|||
msgid "Contributors"
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributorsPage|Building repository graph."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits."
|
||||
msgstr ""
|
||||
|
||||
msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready."
|
||||
msgstr ""
|
||||
|
||||
msgid "Copy URL to clipboard"
|
||||
msgstr ""
|
||||
|
||||
|
@ -635,12 +777,21 @@ msgstr ""
|
|||
msgid "Create empty bare repository"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create merge request"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create new branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create new directory"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create new file"
|
||||
msgstr ""
|
||||
|
||||
msgid "Create new..."
|
||||
msgstr ""
|
||||
|
||||
|
@ -766,6 +917,60 @@ msgstr ""
|
|||
msgid "Emails"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|An error occurred while fetching the environments."
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|An error occurred while making the request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Commit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Deployment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Environment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Environments are places where code gets deployed, such as staging or production."
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Job"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|New environment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|No deployments yet"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Open"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Re-deploy"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Read more about environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Rollback"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Show all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|Updated"
|
||||
msgstr ""
|
||||
|
||||
msgid "Environments|You don't have any environments right now."
|
||||
msgstr ""
|
||||
|
||||
msgid "Error occurred when toggling the notification subscription"
|
||||
msgstr ""
|
||||
|
||||
msgid "EventFilterBy|Filter by all"
|
||||
msgstr ""
|
||||
|
||||
|
@ -805,6 +1010,9 @@ msgstr ""
|
|||
msgid "Failed to remove the pipeline schedule"
|
||||
msgstr ""
|
||||
|
||||
msgid "File name"
|
||||
msgstr ""
|
||||
|
||||
msgid "Files"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1131,6 +1339,12 @@ msgstr ""
|
|||
msgid "No schedules"
|
||||
msgstr ""
|
||||
|
||||
msgid "No time spent"
|
||||
msgstr ""
|
||||
|
||||
msgid "None"
|
||||
msgstr ""
|
||||
|
||||
msgid "Not available"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1194,6 +1408,12 @@ msgstr ""
|
|||
msgid "Notifications"
|
||||
msgstr ""
|
||||
|
||||
msgid "Number of access attempts"
|
||||
msgstr ""
|
||||
|
||||
msgid "Number of failures before backing off"
|
||||
msgstr ""
|
||||
|
||||
msgid "OfSearchInADropdown|Filter"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1455,6 +1675,39 @@ msgstr ""
|
|||
msgid "ProjectsDropdown|This feature requires browser localStorage support"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server."
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|Finding and configuring metrics..."
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|Metrics"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters."
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|Missing environment variable"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|Monitored"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|More information"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment."
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|Prometheus monitoring"
|
||||
msgstr ""
|
||||
|
||||
msgid "PrometheusService|View environments"
|
||||
msgstr ""
|
||||
|
||||
msgid "Public - The group and any public projects can be viewed without any authentication."
|
||||
msgstr ""
|
||||
|
||||
|
@ -1721,9 +1974,15 @@ msgstr ""
|
|||
msgid "Start the Runner!"
|
||||
msgstr ""
|
||||
|
||||
msgid "Stopped"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subgroups"
|
||||
msgstr ""
|
||||
|
||||
msgid "Subscribe"
|
||||
msgstr ""
|
||||
|
||||
msgid "Switch branch/tag"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1738,12 +1997,84 @@ msgstr[1] ""
|
|||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Browse commits"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Browse files"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Can't find HEAD commit for this tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Cancel"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Create tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Delete tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Edit release notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Existing branch name, tag, or commit SHA"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Filter by tag name"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|New Tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|New tag"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Optionally, add a message to the tag."
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page."
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Release notes"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Repository has no tags yet."
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Sort by"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Tags"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Tags give the ability to mark specific points in history as being important"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|This tag has no release notes."
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Use git tag command to add a new one:"
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|Write your release notes or drag files here..."
|
||||
msgstr ""
|
||||
|
||||
msgid "TagsPage|protected"
|
||||
msgstr ""
|
||||
|
||||
msgid "Target Branch"
|
||||
msgstr ""
|
||||
|
||||
msgid "Team"
|
||||
msgstr ""
|
||||
|
||||
msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold"
|
||||
msgstr ""
|
||||
|
||||
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
|
||||
msgstr ""
|
||||
|
||||
|
@ -1756,6 +2087,12 @@ msgstr ""
|
|||
msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage."
|
||||
msgstr ""
|
||||
|
||||
msgid "The number of attempts GitLab will make to access a storage."
|
||||
msgstr ""
|
||||
|
||||
msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host"
|
||||
msgstr ""
|
||||
|
||||
msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}."
|
||||
msgstr ""
|
||||
|
||||
|
@ -1976,6 +2313,9 @@ msgstr ""
|
|||
msgid "Total Time"
|
||||
msgstr ""
|
||||
|
||||
msgid "Total issue time spent"
|
||||
msgstr ""
|
||||
|
||||
msgid "Total test time for all commits/merges"
|
||||
msgstr ""
|
||||
|
||||
|
@ -1988,6 +2328,9 @@ msgstr ""
|
|||
msgid "Unstar"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unsubscribe"
|
||||
msgstr ""
|
||||
|
||||
msgid "Upload New File"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,319 +0,0 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Projects::ClustersController do
|
||||
include AccessMatchersForController
|
||||
include GoogleApi::CloudPlatformHelpers
|
||||
|
||||
set(:project) { create(:project) }
|
||||
|
||||
describe 'GET index' do
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when project has a cluster' do
|
||||
let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
it { expect(go).to redirect_to(namespace_project_cluster_path(project.namespace, project, project.cluster)) }
|
||||
end
|
||||
|
||||
context 'when project does not have a cluster' do
|
||||
it { expect(go).to redirect_to(new_namespace_project_cluster_path(project.namespace, project)) }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :index, namespace_id: project.namespace.to_param, project_id: project
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET status' do
|
||||
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
|
||||
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "responds with matching schema" do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to match_response_schema('cluster_status')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :status, namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster,
|
||||
format: :json
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET show' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "renders view" do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(assigns(:cluster)).to eq(cluster)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
get :show, namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
context 'Managed' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when changing parameters' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
enabled: false,
|
||||
name: 'my-new-cluster-name',
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: 'my-namespace'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it "updates and redirects back to show page" do
|
||||
go
|
||||
|
||||
cluster.reload
|
||||
expect(response).to redirect_to(namespace_project_cluster_path(project.namespace, project, project.cluster))
|
||||
expect(flash[:notice]).to eq('Cluster was successfully updated.')
|
||||
expect(cluster.enabled).to be_falsey
|
||||
end
|
||||
|
||||
it "does not change cluster name" do
|
||||
go
|
||||
|
||||
expect(cluster.name).to eq('test-cluster')
|
||||
end
|
||||
|
||||
context 'when cluster is being created' do
|
||||
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
|
||||
|
||||
it "rejects changes" do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
expect(cluster.enabled).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'User' do
|
||||
let(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'when changing parameters' do
|
||||
let(:params) do
|
||||
{
|
||||
cluster: {
|
||||
enabled: false,
|
||||
name: 'my-new-cluster-name',
|
||||
platform_kubernetes_attributes: {
|
||||
namespace: 'my-namespace'
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it "updates and redirects back to show page" do
|
||||
go
|
||||
|
||||
cluster.reload
|
||||
expect(response).to redirect_to(namespace_project_cluster_path(project.namespace, project, project.cluster))
|
||||
expect(flash[:notice]).to eq('Cluster was successfully updated.')
|
||||
expect(cluster.enabled).to be_falsey
|
||||
expect(cluster.name).to eq('my-new-cluster-name')
|
||||
expect(cluster.platform_kubernetes.namespace).to eq('my-namespace')
|
||||
end
|
||||
|
||||
context 'when cluster is being created' do
|
||||
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
|
||||
|
||||
it "rejects changes" do
|
||||
go
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template(:show)
|
||||
expect(cluster.enabled).to be_truthy
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
set(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
|
||||
|
||||
let(:params) do
|
||||
{ cluster: { enabled: false } }
|
||||
end
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
put :update, params.merge(namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE destroy' do
|
||||
describe 'functionality' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
project.add_master(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
context 'GCP' do
|
||||
context 'when cluster is created' do
|
||||
let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
it "destroys and redirects back to clusters list" do
|
||||
expect { go }
|
||||
.to change { Clusters::Cluster.count }.by(-1)
|
||||
.and change { Clusters::Platforms::Kubernetes.count }.by(-1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(-1)
|
||||
|
||||
expect(response).to redirect_to(namespace_project_clusters_path(project.namespace, project))
|
||||
expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cluster is being created' do
|
||||
let!(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
|
||||
|
||||
it "destroys and redirects back to clusters list" do
|
||||
expect { go }
|
||||
.to change { Clusters::Cluster.count }.by(-1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(-1)
|
||||
|
||||
expect(response).to redirect_to(namespace_project_clusters_path(project.namespace, project))
|
||||
expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'User' do
|
||||
context 'when provider is user' do
|
||||
let!(:cluster) { create(:cluster, :provided_by_user, projects: [project]) }
|
||||
|
||||
it "destroys and redirects back to clusters list" do
|
||||
expect { go }
|
||||
.to change { Clusters::Cluster.count }.by(-1)
|
||||
.and change { Clusters::Platforms::Kubernetes.count }.by(-1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(0)
|
||||
|
||||
expect(response).to redirect_to(namespace_project_clusters_path(project.namespace, project))
|
||||
expect(flash[:notice]).to eq('Cluster integration was successfully removed.')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'security' do
|
||||
set(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
it { expect { go }.to be_allowed_for(:admin) }
|
||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||
it { expect { go }.to be_allowed_for(:master).of(project) }
|
||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||
it { expect { go }.to be_denied_for(:user) }
|
||||
it { expect { go }.to be_denied_for(:external) }
|
||||
end
|
||||
|
||||
def go
|
||||
delete :destroy, namespace_id: project.namespace,
|
||||
project_id: project,
|
||||
id: cluster
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,5 +28,9 @@ FactoryGirl.define do
|
|||
provider_type :gcp
|
||||
provider_gcp factory: [:cluster_provider_gcp, :creating]
|
||||
end
|
||||
|
||||
trait :disabled do
|
||||
enabled false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,10 +11,256 @@ feature 'Clusters', :js do
|
|||
gitlab_sign_in(user)
|
||||
end
|
||||
|
||||
<<<<<<< HEAD
|
||||
context 'when user does not have a cluster and visits cluster index page' do
|
||||
=======
|
||||
context 'when user has signed in Google' do
|
||||
before do
|
||||
allow_any_instance_of(Projects::ClustersController)
|
||||
.to receive(:token_in_session).and_return('token')
|
||||
allow_any_instance_of(Projects::ClustersController)
|
||||
.to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s)
|
||||
end
|
||||
|
||||
context 'when user does not have a cluster and visits cluster index page' do
|
||||
before do
|
||||
visit project_clusters_path(project)
|
||||
end
|
||||
|
||||
it 'sees empty state' do
|
||||
expect(page).to have_link('Add cluster')
|
||||
expect(page).to have_selector('.empty-state')
|
||||
end
|
||||
|
||||
context 'when user opens create on gke page' do
|
||||
before do
|
||||
click_link 'Add cluster'
|
||||
click_link 'Create on GKE'
|
||||
end
|
||||
|
||||
context 'when user filled form with valid parameters' do
|
||||
before do
|
||||
double.tap do |dbl|
|
||||
allow(dbl).to receive(:status).and_return('RUNNING')
|
||||
allow(dbl).to receive(:self_link)
|
||||
.and_return('projects/gcp-project-12345/zones/us-central1-a/operations/ope-123')
|
||||
allow_any_instance_of(GoogleApi::CloudPlatform::Client)
|
||||
.to receive(:projects_zones_clusters_create).and_return(dbl)
|
||||
end
|
||||
|
||||
allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil)
|
||||
|
||||
fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123'
|
||||
fill_in 'cluster_name', with: 'dev-cluster'
|
||||
click_button 'Create cluster'
|
||||
end
|
||||
|
||||
it 'user sees a cluster details page and creation status' do
|
||||
expect(page).to have_content('Cluster is being created on Google Container Engine...')
|
||||
|
||||
# Application Installation buttons
|
||||
page.within('.js-cluster-application-row-helm') do
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page.find(:css, '.js-cluster-application-install-button').text).to eq('Install')
|
||||
end
|
||||
|
||||
Clusters::Cluster.last.provider.make_created!
|
||||
|
||||
expect(page).to have_content('Cluster was successfully created on Google Container Engine')
|
||||
end
|
||||
|
||||
it 'user sees a error if something worng during creation' do
|
||||
expect(page).to have_content('Cluster is being created on Google Container Engine...')
|
||||
|
||||
Clusters::Cluster.last.provider.make_errored!('Something wrong!')
|
||||
|
||||
expect(page).to have_content('Something wrong!')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user filled form with invalid parameters' do
|
||||
before do
|
||||
click_button 'Create cluster'
|
||||
end
|
||||
|
||||
it 'user sees a validation error' do
|
||||
expect(page).to have_css('#error_explanation')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has a cluster and visits cluster index page' do
|
||||
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
|
||||
before do
|
||||
visit project_clusters_path(project)
|
||||
end
|
||||
|
||||
it 'user sees a table with one cluster' do
|
||||
# One is the header row, the other the cluster row
|
||||
expect(page).to have_selector('.gl-responsive-table-row', count: 2)
|
||||
end
|
||||
|
||||
it 'user sees a disabled add cluster button ' do
|
||||
expect(page).to have_selector('.js-add-cluster.disabled')
|
||||
end
|
||||
|
||||
it 'user sees navigation tabs' do
|
||||
expect(page.find('.js-active-tab').text).to include('Active')
|
||||
expect(page.find('.js-active-tab .badge').text).to include('1')
|
||||
|
||||
expect(page.find('.js-inactive-tab').text).to include('Inactive')
|
||||
expect(page.find('.js-inactive-tab .badge').text).to include('0')
|
||||
|
||||
expect(page.find('.js-all-tab').text).to include('All')
|
||||
expect(page.find('.js-all-tab .badge').text).to include('1')
|
||||
end
|
||||
|
||||
context 'update cluster' do
|
||||
it 'user can update cluster' do
|
||||
expect(page).to have_selector('.js-toggle-cluster-list')
|
||||
end
|
||||
|
||||
context 'with sucessfull request' do
|
||||
it 'user sees updated cluster' do
|
||||
expect do
|
||||
page.find('.js-toggle-cluster-list').click
|
||||
wait_for_requests
|
||||
end.to change { cluster.enabled }
|
||||
|
||||
expect(page).not_to have_selector('.is-checked')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with failed request' do
|
||||
it 'user sees not update cluster and error message' do
|
||||
# Cluster was disabled in the last test
|
||||
page.find('.js-toggle-cluster-list').click
|
||||
|
||||
Clusters::Cluster.last.provider.make_errored!('Something wrong!')
|
||||
|
||||
expect(page).not_to have_selector('.js-toggle-cluster-list.is-checked')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user clicks on a cluster' do
|
||||
before do
|
||||
click_link cluster.name
|
||||
end
|
||||
|
||||
it 'user sees an cluster details page' do
|
||||
expect(page).to have_button('Save')
|
||||
expect(page.find(:css, '.cluster-name').value).to eq(cluster.name)
|
||||
|
||||
# Application Installation buttons
|
||||
page.within('.js-cluster-application-row-helm') do
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to be_nil
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user installs application: Helm Tiller' do
|
||||
before do
|
||||
allow(ClusterInstallAppWorker).to receive(:perform_async).and_return(nil)
|
||||
|
||||
page.within('.js-cluster-application-row-helm') do
|
||||
page.find(:css, '.js-cluster-application-install-button').click
|
||||
end
|
||||
end
|
||||
|
||||
it 'user sees status transition' do
|
||||
page.within('.js-cluster-application-row-helm') do
|
||||
# FE sends request and gets the response, then the buttons is "Install"
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install')
|
||||
|
||||
Clusters::Cluster.last.application_helm.make_installing!
|
||||
|
||||
# FE starts polling and update the buttons to "Installing"
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installing')
|
||||
|
||||
Clusters::Cluster.last.application_helm.make_installed!
|
||||
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed')
|
||||
end
|
||||
|
||||
expect(page).to have_content('Helm Tiller was successfully installed on your cluster')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user installs application: Ingress' do
|
||||
before do
|
||||
allow(ClusterInstallAppWorker).to receive(:perform_async).and_return(nil)
|
||||
# Helm Tiller needs to be installed before you can install Ingress
|
||||
create(:cluster_applications_helm, :installed, cluster: cluster)
|
||||
|
||||
visit project_cluster_path(project, cluster)
|
||||
|
||||
page.within('.js-cluster-application-row-ingress') do
|
||||
page.find(:css, '.js-cluster-application-install-button').click
|
||||
end
|
||||
end
|
||||
|
||||
it 'user sees status transition' do
|
||||
page.within('.js-cluster-application-row-ingress') do
|
||||
# FE sends request and gets the response, then the buttons is "Install"
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Install')
|
||||
|
||||
Clusters::Cluster.last.application_ingress.make_installing!
|
||||
|
||||
# FE starts polling and update the buttons to "Installing"
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installing')
|
||||
|
||||
Clusters::Cluster.last.application_ingress.make_installed!
|
||||
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')['disabled']).to eq('true')
|
||||
expect(page.find(:css, '.js-cluster-application-install-button')).to have_content('Installed')
|
||||
end
|
||||
|
||||
expect(page).to have_content('Ingress was successfully installed on your cluster')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user disables the cluster' do
|
||||
before do
|
||||
page.find(:css, '.js-toggle-cluster').click
|
||||
click_button 'Save'
|
||||
end
|
||||
|
||||
it 'user sees the successful message' do
|
||||
expect(page).to have_content('Cluster was successfully updated.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user destroys the cluster' do
|
||||
before do
|
||||
page.accept_confirm do
|
||||
click_link 'Remove integration'
|
||||
end
|
||||
end
|
||||
|
||||
it 'user sees creation form with the succeccful message' do
|
||||
expect(page).to have_content('Cluster integration was successfully removed.')
|
||||
expect(page).to have_link('Add cluster')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has not signed in Google' do
|
||||
>>>>>>> origin/list-multiple-clusters
|
||||
before do
|
||||
visit project_clusters_path(project)
|
||||
|
||||
click_link 'Add cluster'
|
||||
click_link 'Create on GKE'
|
||||
end
|
||||
|
||||
|
@ -22,4 +268,6 @@ feature 'Clusters', :js do
|
|||
expect(page).to have_button('Create cluster')
|
||||
end
|
||||
end
|
||||
|
||||
context
|
||||
end
|
||||
|
|
|
@ -177,7 +177,7 @@ describe 'Edit Project Settings' do
|
|||
click_button "Save changes"
|
||||
end
|
||||
|
||||
expect(find(".sharing-permissions")).to have_selector(".project-feature-toggle.disabled", count: 2)
|
||||
expect(find(".sharing-permissions")).to have_selector(".project-feature-toggle.is-disabled", count: 2)
|
||||
end
|
||||
|
||||
it "shows empty features project homepage" do
|
||||
|
@ -272,10 +272,10 @@ describe 'Edit Project Settings' do
|
|||
end
|
||||
|
||||
def toggle_feature_off(feature_name)
|
||||
find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle.checked").click
|
||||
find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle.is-checked").click
|
||||
end
|
||||
|
||||
def toggle_feature_on(feature_name)
|
||||
find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle:not(.checked)").click
|
||||
find(".project-feature-controls[data-for=\"#{feature_name}\"] .project-feature-toggle:not(.is-checked)").click
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ClustersFinder do
|
||||
let(:project) { create(:project) }
|
||||
set(:user) { create(:user) }
|
||||
|
||||
describe '#execute' do
|
||||
let(:enabled_cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
|
||||
let(:disabled_cluster) { create(:cluster, :disabled, :provided_by_gcp, projects: [project]) }
|
||||
|
||||
subject { described_class.new(project, user, scope).execute }
|
||||
|
||||
context 'when scope is all' do
|
||||
let(:scope) { :all }
|
||||
|
||||
it { is_expected.to match_array([enabled_cluster, disabled_cluster]) }
|
||||
end
|
||||
|
||||
context 'when scope is active' do
|
||||
let(:scope) { :active }
|
||||
|
||||
it { is_expected.to match_array([enabled_cluster]) }
|
||||
end
|
||||
|
||||
context 'when scope is inactive' do
|
||||
let(:scope) { :inactive }
|
||||
|
||||
it { is_expected.to match_array([disabled_cluster]) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe ClustersHelper do
|
||||
let(:cluster) { create(:cluster) }
|
||||
|
||||
describe '.can_toggle_cluster' do
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
allow(helper).to receive(:current_user).and_return(user)
|
||||
end
|
||||
|
||||
subject { helper.can_toggle_cluster?(cluster) }
|
||||
|
||||
context 'when user can update' do
|
||||
before do
|
||||
allow(helper).to receive(:can?).with(any_args).and_return(true)
|
||||
end
|
||||
|
||||
context 'when cluster is created' do
|
||||
before do
|
||||
allow(cluster).to receive(:created?).and_return(true)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when cluster is not created' do
|
||||
before do
|
||||
allow(cluster).to receive(:created?).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user can not update' do
|
||||
before do
|
||||
allow(helper).to receive(:can?).with(any_args).and_return(false)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,7 +28,7 @@ describe('Clusters', () => {
|
|||
|
||||
expect(
|
||||
cluster.toggleButton.classList,
|
||||
).not.toContain('checked');
|
||||
).not.toContain('is-checked');
|
||||
|
||||
expect(
|
||||
cluster.toggleInput.getAttribute('value'),
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import setClusterTableToggles from '~/clusters/clusters_index';
|
||||
import { setTimeout } from 'core-js/library/web/timers';
|
||||
|
||||
describe('Clusters table', () => {
|
||||
preloadFixtures('clusters/index_cluster.html.raw');
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
loadFixtures('clusters/index_cluster.html.raw');
|
||||
mock = new MockAdapter(axios);
|
||||
setClusterTableToggles();
|
||||
});
|
||||
|
||||
describe('update cluster', () => {
|
||||
it('renders loading state while request is made', () => {
|
||||
const button = document.querySelector('.js-toggle-cluster-list');
|
||||
|
||||
button.click();
|
||||
|
||||
expect(button.classList).toContain('is-loading');
|
||||
expect(button.getAttribute('disabled')).toEqual('true');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('shows updated state after sucessfull request', (done) => {
|
||||
mock.onPut().reply(200, {}, {});
|
||||
const button = document.querySelector('.js-toggle-cluster-list');
|
||||
button.click();
|
||||
|
||||
expect(button.classList).toContain('is-loading');
|
||||
|
||||
setTimeout(() => {
|
||||
expect(button.classList).not.toContain('is-loading');
|
||||
expect(button.classList).not.toContain('is-checked');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
|
||||
it('shows inital state after failed request', (done) => {
|
||||
mock.onPut().reply(500, {}, {});
|
||||
const button = document.querySelector('.js-toggle-cluster-list');
|
||||
|
||||
button.click();
|
||||
expect(button.classList).toContain('is-loading');
|
||||
|
||||
setTimeout(() => {
|
||||
expect(button.classList).not.toContain('is-loading');
|
||||
expect(button.classList).toContain('is-checked');
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -31,4 +31,19 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
|
|||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
|
||||
context 'rendering non-empty state' do
|
||||
before do
|
||||
cluster
|
||||
end
|
||||
|
||||
it 'clusters/index_cluster.html.raw' do |example|
|
||||
get :index,
|
||||
namespace_id: namespace,
|
||||
project_id: project
|
||||
|
||||
expect(response).to be_success
|
||||
store_frontend_fixture(response, example.description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import Vue from 'vue';
|
||||
import toggleButton from '~/vue_shared/components/toggle_button.vue';
|
||||
import mountComponent from '../../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Toggle Button', () => {
|
||||
let vm;
|
||||
let Component;
|
||||
|
||||
beforeEach(() => {
|
||||
Component = Vue.extend(toggleButton);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('render output', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
value: true,
|
||||
name: 'foo',
|
||||
});
|
||||
});
|
||||
|
||||
it('renders input with provided name', () => {
|
||||
expect(vm.$el.querySelector('input').getAttribute('name')).toEqual('foo');
|
||||
});
|
||||
|
||||
it('renders input with provided value', () => {
|
||||
expect(vm.$el.querySelector('input').getAttribute('value')).toEqual('true');
|
||||
});
|
||||
|
||||
it('renders Enabled and Disabled text data attributes', () => {
|
||||
expect(vm.$el.querySelector('button').getAttribute('data-enabled-text')).toEqual('Enabled');
|
||||
expect(vm.$el.querySelector('button').getAttribute('data-disabled-text')).toEqual('Disabled');
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-checked', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
value: true,
|
||||
});
|
||||
|
||||
spyOn(vm, '$emit');
|
||||
});
|
||||
|
||||
it('renders is checked class', () => {
|
||||
expect(vm.$el.querySelector('button').classList.contains('is-checked')).toEqual(true);
|
||||
});
|
||||
|
||||
it('emits change event when clicked', () => {
|
||||
vm.$el.querySelector('button').click();
|
||||
|
||||
expect(vm.$emit).toHaveBeenCalledWith('change', false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-disabled', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
value: true,
|
||||
disabledInput: true,
|
||||
});
|
||||
spyOn(vm, '$emit');
|
||||
});
|
||||
|
||||
it('renders disabled button', () => {
|
||||
expect(vm.$el.querySelector('button').classList.contains('is-disabled')).toEqual(true);
|
||||
});
|
||||
|
||||
it('does not emit change event when clicked', () => {
|
||||
vm.$el.querySelector('button').click();
|
||||
|
||||
expect(vm.$emit).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('is-loading', () => {
|
||||
beforeEach(() => {
|
||||
vm = mountComponent(Component, {
|
||||
value: true,
|
||||
isLoading: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders loading class', () => {
|
||||
expect(vm.$el.querySelector('button').classList.contains('is-loading')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -198,4 +198,26 @@ describe Clusters::Cluster do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#created?' do
|
||||
let(:cluster) { create(:cluster, :provided_by_gcp) }
|
||||
|
||||
subject { cluster.created? }
|
||||
|
||||
context 'when status_name is :created' do
|
||||
before do
|
||||
allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:created)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when status_name is not :created' do
|
||||
before do
|
||||
allow(cluster).to receive_message_chain(:provider, :status_name).and_return(:creating)
|
||||
end
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,7 +7,63 @@ describe Clusters::CreateService do
|
|||
let(:result) { described_class.new(project, user, params).execute(access_token) }
|
||||
|
||||
context 'when provider is gcp' do
|
||||
context 'when correct params' do
|
||||
context 'when project has no clusters' do
|
||||
context 'when correct params' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'test-cluster',
|
||||
provider_type: :gcp,
|
||||
provider_gcp_attributes: {
|
||||
gcp_project_id: 'gcp-project',
|
||||
zone: 'us-central1-a',
|
||||
num_nodes: 1,
|
||||
machine_type: 'machine_type-a'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a cluster object and performs a worker' do
|
||||
expect(ClusterProvisionWorker).to receive(:perform_async)
|
||||
|
||||
expect { result }
|
||||
.to change { Clusters::Cluster.count }.by(1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(1)
|
||||
|
||||
expect(result.name).to eq('test-cluster')
|
||||
expect(result.user).to eq(user)
|
||||
expect(result.project).to eq(project)
|
||||
expect(result.provider.gcp_project_id).to eq('gcp-project')
|
||||
expect(result.provider.zone).to eq('us-central1-a')
|
||||
expect(result.provider.num_nodes).to eq(1)
|
||||
expect(result.provider.machine_type).to eq('machine_type-a')
|
||||
expect(result.provider.access_token).to eq(access_token)
|
||||
expect(result.platform).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid params' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'test-cluster',
|
||||
provider_type: :gcp,
|
||||
provider_gcp_attributes: {
|
||||
gcp_project_id: '!!!!!!!',
|
||||
zone: 'us-central1-a',
|
||||
num_nodes: 1,
|
||||
machine_type: 'machine_type-a'
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns an error' do
|
||||
expect(ClusterProvisionWorker).not_to receive(:perform_async)
|
||||
expect { result }.to change { Clusters::Cluster.count }.by(0)
|
||||
expect(result.errors[:"provider_gcp.gcp_project_id"]).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when project has a cluster' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'test-cluster',
|
||||
|
@ -21,43 +77,13 @@ describe Clusters::CreateService do
|
|||
}
|
||||
end
|
||||
|
||||
it 'creates a cluster object and performs a worker' do
|
||||
expect(ClusterProvisionWorker).to receive(:perform_async)
|
||||
|
||||
expect { result }
|
||||
.to change { Clusters::Cluster.count }.by(1)
|
||||
.and change { Clusters::Providers::Gcp.count }.by(1)
|
||||
|
||||
expect(result.name).to eq('test-cluster')
|
||||
expect(result.user).to eq(user)
|
||||
expect(result.project).to eq(project)
|
||||
expect(result.provider.gcp_project_id).to eq('gcp-project')
|
||||
expect(result.provider.zone).to eq('us-central1-a')
|
||||
expect(result.provider.num_nodes).to eq(1)
|
||||
expect(result.provider.machine_type).to eq('machine_type-a')
|
||||
expect(result.provider.access_token).to eq(access_token)
|
||||
expect(result.platform).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when invalid params' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'test-cluster',
|
||||
provider_type: :gcp,
|
||||
provider_gcp_attributes: {
|
||||
gcp_project_id: '!!!!!!!',
|
||||
zone: 'us-central1-a',
|
||||
num_nodes: 1,
|
||||
machine_type: 'machine_type-a'
|
||||
}
|
||||
}
|
||||
before do
|
||||
Clusters::Cluster.create(params.merge(user: user, projects: [project]))
|
||||
end
|
||||
|
||||
it 'returns an error' do
|
||||
it 'does not create a cluster' do
|
||||
expect(ClusterProvisionWorker).not_to receive(:perform_async)
|
||||
expect { result }.to change { Clusters::Cluster.count }.by(0)
|
||||
expect(result.errors[:"provider_gcp.gcp_project_id"]).to be_present
|
||||
expect { result }.to raise_error(Exception).and change { Clusters::Cluster.count }.by(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue