Hide cluster details until cluster is created

Only display the details of the cluster page when the cluster
exists. If it is in "creating" state, show a message and a spinner
This commit is contained in:
Mike Greiling 2019-08-02 20:08:53 +00:00 committed by Fatih Acet
parent 57e00e191d
commit 9fb1df8f97
8 changed files with 126 additions and 59 deletions

View File

@ -48,6 +48,9 @@ export default class Clusters {
} = document.querySelector('.js-edit-cluster-form').dataset; } = document.querySelector('.js-edit-cluster-form').dataset;
this.clusterId = clusterId; this.clusterId = clusterId;
this.clusterNewlyCreatedKey = `cluster_${this.clusterId}_newly_created`;
this.clusterBannerDismissedKey = `cluster_${this.clusterId}_banner_dismissed`;
this.store = new ClustersStore(); this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath); this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath); this.store.setManagePrometheusPath(managePrometheusPath);
@ -81,18 +84,19 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token'); this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token'); this.tokenField = document.querySelector('.js-cluster-token');
this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text'); this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
this.ingressDomainSnippet = this.ingressDomainHelpText.querySelector( this.ingressDomainSnippet =
'.js-ingress-domain-snippet', this.ingressDomainHelpText &&
); this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet');
Clusters.initDismissableCallout(); Clusters.initDismissableCallout();
initSettingsPanels(); initSettingsPanels();
setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area')); const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area');
if (toggleButtonsContainer) {
setupToggleButtons(toggleButtonsContainer);
}
this.initApplications(clusterType); this.initApplications(clusterType);
if (this.store.state.status !== 'created') { this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
}
this.addListeners(); this.addListeners();
if (statusPath) { if (statusPath) {
@ -247,35 +251,56 @@ export default class Clusters {
setBannerDismissedState(status, isDismissed) { setBannerDismissedState(status, isDismissed) {
if (AccessorUtilities.isLocalStorageAccessSafe()) { if (AccessorUtilities.isLocalStorageAccessSafe()) {
window.localStorage.setItem( window.localStorage.setItem(this.clusterBannerDismissedKey, `${status}_${isDismissed}`);
`cluster_${this.clusterId}_banner_dismissed`,
`${status}_${isDismissed}`,
);
} }
} }
isBannerDismissed(status) { isBannerDismissed(status) {
let bannerState; let bannerState;
if (AccessorUtilities.isLocalStorageAccessSafe()) { if (AccessorUtilities.isLocalStorageAccessSafe()) {
bannerState = window.localStorage.getItem(`cluster_${this.clusterId}_banner_dismissed`); bannerState = window.localStorage.getItem(this.clusterBannerDismissedKey);
} }
return bannerState === `${status}_true`; return bannerState === `${status}_true`;
} }
updateContainer(prevStatus, status, error) { setClusterNewlyCreated(state) {
this.hideAll(); if (AccessorUtilities.isLocalStorageAccessSafe()) {
window.localStorage.setItem(this.clusterNewlyCreatedKey, Boolean(state));
}
}
if (this.isBannerDismissed(status)) { isClusterNewlyCreated() {
// once this is true, it will always be true for a given page load
if (!this.isNewlyCreated) {
let newlyCreated;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
newlyCreated = window.localStorage.getItem(this.clusterNewlyCreatedKey);
}
this.isNewlyCreated = newlyCreated === 'true';
}
return this.isNewlyCreated;
}
updateContainer(prevStatus, status, error) {
if (status !== 'created' && this.isBannerDismissed(status)) {
return; return;
} }
this.setBannerDismissedState(status, false); this.setBannerDismissedState(status, false);
// We poll all the time but only want the `created` banner to show when newly created if (prevStatus !== status) {
if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) { this.hideAll();
switch (status) { switch (status) {
case 'created': case 'created':
this.successContainer.classList.remove('hidden'); if (this.isClusterNewlyCreated()) {
this.setClusterNewlyCreated(false);
this.successContainer.classList.remove('hidden');
} else if (prevStatus) {
this.setClusterNewlyCreated(true);
window.location.reload();
}
break; break;
case 'errored': case 'errored':
this.errorContainer.classList.remove('hidden'); this.errorContainer.classList.remove('hidden');
@ -292,7 +317,6 @@ export default class Clusters {
this.creatingContainer.classList.remove('hidden'); this.creatingContainer.classList.remove('hidden');
break; break;
default: default:
this.hideAll();
} }
} }
} }

View File

@ -28,8 +28,10 @@ const setState = glManagedCheckbox => {
const initGkeNamespace = () => { const initGkeNamespace = () => {
const glManagedCheckbox = document.querySelector('.js-gl-managed'); const glManagedCheckbox = document.querySelector('.js-gl-managed');
setState(glManagedCheckbox); // this is needed in order to set the initial state if (glManagedCheckbox) {
glManagedCheckbox.addEventListener('change', () => setState(glManagedCheckbox)); setState(glManagedCheckbox); // this is needed in order to set the initial state
glManagedCheckbox.addEventListener('change', () => setState(glManagedCheckbox));
}
}; };
export default initGkeNamespace; export default initGkeNamespace;

View File

@ -3,7 +3,8 @@
%p.js-error-reason %p.js-error-reason
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' } .hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...') %span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' }
%span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' } .hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
.col-11 .col-11
@ -18,4 +19,4 @@
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×" %button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' } .hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
= s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine.")

View File

@ -33,26 +33,29 @@
%section#cluster-integration %section#cluster-integration
%h4= @cluster.name %h4= @cluster.name
= render 'banner' = render 'banner'
= render 'form'
= render_if_exists 'projects/clusters/prometheus_graphs' - unless @cluster.status_name.in? %i/scheduled creating/
= render 'form'
.cluster-applications-table#js-cluster-applications - unless @cluster.status_name.in? %i/scheduled creating/
= render_if_exists 'projects/clusters/prometheus_graphs'
%section.settings#js-cluster-details{ class: ('expanded' if expanded) } .cluster-applications-table#js-cluster-applications
.settings-header
%h4= s_('ClusterIntegration|Kubernetes cluster details')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
.settings-content
= render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster)
%section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) } %section.settings#js-cluster-details{ class: ('expanded' if expanded) }
.settings-header .settings-header
%h4= _('Advanced settings') %h4= s_('ClusterIntegration|Kubernetes cluster details')
%button.btn.js-settings-toggle{ type: 'button' } %button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand') = expanded ? _('Collapse') : _('Expand')
%p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration") %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
.settings-content#advanced-settings-section .settings-content
= render 'advanced_settings' = render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster)
%section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
.settings-header
%h4= _('Advanced settings')
%button.btn.js-settings-toggle{ type: 'button' }
= expanded ? _('Collapse') : _('Expand')
%p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
.settings-content#advanced-settings-section
= render 'advanced_settings'

View File

@ -0,0 +1,5 @@
---
title: Update cluster page automatically when cluster is created
merge_request: 27189
author:
type: changed

View File

@ -2647,7 +2647,7 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster name" msgid "ClusterIntegration|Kubernetes cluster name"
msgstr "" msgstr ""
msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine."
msgstr "" msgstr ""
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."

View File

@ -22,9 +22,8 @@ describe 'Clusters Applications', :js do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) } let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
it 'user is unable to install applications' do it 'user is unable to install applications' do
page.within('.js-cluster-application-row-helm') do expect(page).not_to have_css('.js-cluster-application-row-helm')
expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Install') expect(page).not_to have_css('.js-cluster-application-install-button')
end
end end
end end

View File

@ -147,47 +147,80 @@ describe('Clusters', () => {
}); });
describe('updateContainer', () => { describe('updateContainer', () => {
const { location } = window;
beforeEach(() => {
delete window.location;
window.location = {
reload: jest.fn(),
hash: location.hash,
};
});
afterEach(() => {
window.location = location;
});
describe('when creating cluster', () => { describe('when creating cluster', () => {
it('should show the creating container', () => { it('should show the creating container', () => {
cluster.updateContainer(null, 'creating'); cluster.updateContainer(null, 'creating');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
expect(window.location.reload).not.toHaveBeenCalled();
}); });
it('should continue to show `creating` banner with subsequent updates of the same status', () => { it('should continue to show `creating` banner with subsequent updates of the same status', () => {
cluster.updateContainer(null, 'creating');
cluster.updateContainer('creating', 'creating'); cluster.updateContainer('creating', 'creating');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy(); expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
expect(window.location.reload).not.toHaveBeenCalled();
}); });
}); });
describe('when cluster is created', () => { describe('when cluster is created', () => {
it('should show the success container and fresh the page', () => { it('should hide the "creating" banner and refresh the page', () => {
cluster.updateContainer(null, 'created'); jest.spyOn(cluster, 'setClusterNewlyCreated');
cluster.updateContainer(null, 'creating');
cluster.updateContainer('creating', 'created');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
expect(window.location.reload).toHaveBeenCalled();
expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(true);
}); });
it('should not show a banner when status is already `created`', () => { it('when the page is refreshed, it should show the "success" banner', () => {
jest.spyOn(cluster, 'setClusterNewlyCreated');
jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(true);
cluster.updateContainer(null, 'created');
cluster.updateContainer('created', 'created'); cluster.updateContainer('created', 'created');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy(); expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
expect(window.location.reload).not.toHaveBeenCalled();
expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(false);
});
it('should not show a banner when status is already `created`', () => {
jest.spyOn(cluster, 'setClusterNewlyCreated');
jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(false);
cluster.updateContainer(null, 'created');
cluster.updateContainer('created', 'created');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
expect(window.location.reload).not.toHaveBeenCalled();
expect(cluster.setClusterNewlyCreated).not.toHaveBeenCalled();
}); });
}); });