Handle toggle button with API request

Add tests
Update empty state
[ci skip]
上级 54cf7dac
......@@ -47,9 +47,15 @@ export default class ClusterTable {
* @param {HTMLElement} button
*/
static toggleLoadingButton(button) {
button.setAttribute('disabled', button.getAttribute('disabled'));
if (button.getAttribute('disabled')) {
button.removeAttribute('disabled');
} else {
button.setAttribute('disabled', true);
}
button.classList.toggle('disabled');
button.classList.toggle('loading');
button.classList.toggle('is-loading');
button.querySelector('.loading-icon').classList.toggle('hidden');
}
/**
......
......@@ -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";
......
......@@ -348,3 +348,12 @@
}
}
}
.flex-container-block {
display: -webkit-flex;
display: flex;
}
.flex-right {
margin-left: auto;
}
......@@ -454,11 +454,3 @@ img.emoji {
.inline { display: inline-block; }
.center { text-align: center; }
.vertical-align-middle { vertical-align: middle; }
.flex-justify-content-center { justify-content: center; }
.flex-wrap { flex-wrap: wrap; }
.flex-right { margin-left: auto; }
.flex-container-block {
display: -webkit-flex;
display: flex;
}
/**
* Toggle button
*
* @usage
* ### Active text
* <button type="button" class="project-feature-toggle checked" data-enabled-text="Enabled" data-disabled-text="Disabled">
* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
* </button>
* ### Disabled text
* <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>
* ### Disabled button
* <button type="button" class="project-feature-toggle disabled" data-enabled-text="Enabled" data-disabled-text="Disabled" disabled="true">
* <i class="fa fa-spinner fa-spin loading-icon hidden"></i>
* </button>
* ### Loading
* <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;
}
&.is-loading {
&::before {
left: 38px;
right: 5px;
}
.loading-icon {
position: absolute;
left: 28px;
font-size: $tooltip-font-size;
color: $white-light;
top: 6px;
}
}
&.checked {
background: $feature-toggle-color-enabled;
&.is-loading {
&::before {
left: 10px;
right: 42px;
animation: animate-enabled .2s ease-in;
content: attr(data-enabled-text);
}
.loading-icon {
left: 60px;
top: 6px;
}
}
&::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; }
}
}
......@@ -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,6 +8,8 @@ class Projects::ClustersController < Projects::ApplicationController
def index
@clusters ||= project.clusters.page(params[:page]).per(20).map { |cluster| cluster.present(current_user: current_user) }
@clusters_count = @clusters.count
end
def login
......
.empty-state.flex-justify-content-center.flex-container-block.flex-wrap
%div
%h2= 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}
.row.empty-state
.col-xs-12
.svg-content= image_tag 'illustrations/labels.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', title: 'Add cluster'
%p
= link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success', title: 'Add cluster'
.svg-content
= image_tag 'illustrations/labels.svg'
......@@ -2,26 +2,26 @@
= render "empty_state"
- else
.top-area.scrolling-tabs-container.inner-page-scroll-tabs
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
.fade-left= icon("angle-left")
.fade-right= icon("angle-right")
%ul.nav-links.scrolling-tabs
%li
%a
= s_('ClusterIntegration|Active')
%a.js-active-tab
=s_("ClusterIntegration|Active")
%span.badge
0
TODO
%li
%a
= s_('ClusterIntegration|Inactive')
%a.js-inactive-tab
= s_("ClusterIntegration|Inactive")
%span.badge
0
TODO
%li
%a
= s_('ClusterIntegration|All')
%span.badge
0
%a.js-all-tab
= s_("ClusterIntegration|All")
%span.badge= @clusters_count
.nav-controls
= link_to s_('ClusterIntegration|Add cluster'), new_project_cluster_path(@project), class: 'btn btn-success', title: 'Add cluster'
= 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")
.ci-table.js-clusters-list
.gl-responsive-table-row.table-row-header{ role: 'row' }
.table-section.section-30{ role: 'rowheader' }
......@@ -35,17 +35,17 @@
.gl-responsive-table-row
.table-section.section-30
.table-mobile-header{ role: 'rowheader' }= s_('ClusterIntegration|Cluster')
.table-mobile-content= cluster.name
.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
Content goes here
.table-mobile-content= cluster.environment_scope
.table-section.section-30
.table-mobile-header{ role: 'rowheader' }
= s_('ClusterIntegration|Project namespace')
.table-mobile-content
Content goes here
Content goes here - TODO
.table-section.section-10
.table-mobile-header{ role: 'rowheader' }
.table-mobile-content
......@@ -56,5 +56,5 @@
data: { 'enabled-text': 'Enabled',
'disabled-text': 'Disabled',
endpoint: namespace_project_cluster_path(@project.namespace, @project, cluster, format: :json) } }
= icon('loading', class: 'hidden')
= icon('spinner spin', class: 'hidden loading-icon')
......@@ -95,23 +95,32 @@ feature 'Clusters', :js do
end
it 'user sees a table with one cluster' do
expect(page).to have_selector('.gl-responsive-table-row', count: 2)
end
it 'user sees a disabled add cluster button ' do
expect(page.find(:css, '.js-add-cluster')['disabled']).to eq('true')
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
end
end
......
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import ClusterTable from '~/clusters/clusters_index';
import { setTimeout } from 'core-js/library/web/timers';
describe('Clusters table', () => {
preloadFixtures('clusters/index_cluster.html.raw');
let ClustersClass;
let mock;
beforeEach(() => {
loadFixtures('clusters/index_cluster.html.raw');
ClustersClass = new ClusterTable();
mock = new MockAdapter(axios);
});
afterEach(() => {
......@@ -13,19 +20,44 @@ describe('Clusters table', () => {
describe('update cluster', () => {
it('renders a toggle button', () => {
expect(document.querySelector('.js-toggle-cluster-list')).not.toBeNull();
});
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.classList).toContain('disabled');
});
afterEach(() => {
mock.restore();
});
it('shows updated state after sucessfull request', () => {
it('shows updated state after sucessfull request', (done) => {
mock.onPut().reply(200, {}, {});
const button = document.querySelector('.js-toggle-cluster-list');
button.click();
setTimeout(() => {
expect(button.classList).toContain('is-loading');
done();
}, 0);
});
it('shows inital state after failed request', () => {
it('shows inital state after failed request', (done) => {
mock.onPut().reply(500, {}, {});
const button = document.querySelector('.js-toggle-cluster-list');
button.click();
setTimeout(() => {
expect(button.classList).toContain('is-loading');
done();
}, 0);
});
});
});
......@@ -31,4 +31,13 @@ describe Projects::ClustersController, '(JavaScript fixtures)', type: :controlle
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
it 'clusters/index_cluster.html.raw' do |example|
get :index,
namespace_id: project.namespace.to_param,
project_id: project
expect(response).to be_success
store_frontend_fixture(response, example.description)
end
end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册