-import { getJSON } from '../helpers/request.js';
-import $ from 'jquery';
+import { getJSON, postJSON, post } from '../helpers/request.js';
+
export function getComponents (data) {
let url = baseUrl + '/api/components/search';
- return $.get(url, data);
+ return getJSON(url, data);
}
export function getProvisioned (data) {
let url = baseUrl + '/api/projects/provisioned';
- return $.get(url, data);
+ return getJSON(url, data);
}
export function getGhosts (data) {
let url = baseUrl + '/api/projects/ghosts';
- return $.get(url, data);
+ return getJSON(url, data);
}
export function deleteComponents (data) {
let url = baseUrl + '/api/projects/bulk_delete';
- return $.post(url, data);
+ return post(url, data);
}
-export function createProject (options) {
- options.url = baseUrl + '/api/projects/create';
- options.type = 'POST';
- return $.ajax(options);
+export function createProject (data) {
+ let url = baseUrl + '/api/projects/create';
+ return postJSON(url, data);
}
export function getChildren (componentKey, metrics = []) {
export default React.createClass({
getInitialState() {
- return { permissions: [] };
+ return { ready: false, permissions: [] };
},
componentDidMount() {
requestPermissions() {
const url = `${window.baseUrl}/api/permissions/search_global_permissions`;
$.get(url).done(r => {
- this.setState({ permissions: r.permissions });
+ this.setState({ ready: true, permissions: r.permissions });
});
},
+ renderSpinner () {
+ if (this.state.ready) {
+ return null;
+ }
+ return <i className="spinner"/>;
+ },
+
render() {
return (
<div className="page">
<header id="global-permissions-header" className="page-header">
<h1 className="page-title">{window.t('global_permissions.page')}</h1>
+ {this.renderSpinner()}
<p className="page-description">{window.t('global_permissions.page.description')}</p>
</header>
- <PermissionsList permissions={this.state.permissions}/>
+ <PermissionsList ready={this.state.ready} permissions={this.state.permissions}/>
</div>
);
}
+import classNames from 'classnames';
import React from 'react';
+
import Permission from './permission';
+
export default React.createClass({
propTypes:{
permissions: React.PropTypes.arrayOf(React.PropTypes.object).isRequired
},
render() {
- return <ul id="global-permissions-list">{this.renderPermissions()}</ul>;
+ let className = classNames({ 'new-loading': !this.props.ready });
+ return <ul id="global-permissions-list" className={className}>{this.renderPermissions()}</ul>;
}
});
export default Marionette.ItemView.extend({
template: Template,
+ collectionEvents: {
+ 'request': 'showSpinner',
+ 'sync': 'hideSpinner'
+ },
+
events: {
'click #groups-create': 'onCreateClick'
},
+ showSpinner: function () {
+ this.$('.spinner').removeClass('hidden');
+ },
+
+ hideSpinner: function () {
+ this.$('.spinner').addClass('hidden');
+ },
+
onCreateClick: function (e) {
e.preventDefault();
this.createGroup();
export default Marionette.CollectionView.extend({
tagName: 'ul',
- childView: ListItemView
+ childView: ListItemView,
+
+ collectionEvents: {
+ 'request': 'showLoading',
+ 'sync': 'hideLoading'
+ },
+
+ showLoading: function () {
+ this.$el.addClass('new-loading');
+ },
+
+ hideLoading: function () {
+ this.$el.removeClass('new-loading');
+ }
});
<header class="page-header">
<h1 class="page-title">{{t 'user_groups.page'}}</h1>
+ <i class="spinner hidden"></i>
<div class="page-actions">
<div class="button-group">
<button id="groups-create">Create Group</button>
}).render();
},
+ renderSpinner () {
+ if (this.props.ready) {
+ return null;
+ }
+ return <i className="spinner"/>;
+ },
+
render() {
return (
<header id="project-permissions-header" className="page-header">
<h1 className="page-title">{window.t('permission_templates.page')}</h1>
+ {this.renderSpinner()}
<div className="page-actions">
<button onClick={this.onCreate}>Create</button>
</div>
},
getInitialState() {
- return { permissions: [], permissionTemplates: [] };
+ return { ready: false, permissions: [], permissionTemplates: [] };
},
componentDidMount() {
let permissionTemplates = this.mergePermissionsToTemplates(r.permissionTemplates, permissions);
let permissionTemplatesWithDefaults = this.mergeDefaultsToTemplates(permissionTemplates, r.defaultTemplates);
this.setState({
+ ready: true,
permissionTemplates: permissionTemplatesWithDefaults,
permissions: permissions
});
render() {
return (
<div className="page">
- <Header
- refresh={this.requestPermissions}/>
+ <Header ready={this.state.ready} refresh={this.requestPermissions}/>
<PermissionTemplates
+ ready={this.state.ready}
permissionTemplates={this.state.permissionTemplates}
permissions={this.state.permissions}
topQualifiers={this.props.topQualifiers}
+import classNames from 'classnames';
import React from 'react';
+
import PermissionsHeader from './permissions-header';
import PermissionTemplate from './permission-template';
+
export default React.createClass({
propTypes:{
permissionTemplates: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
topQualifiers={this.props.topQualifiers}
refresh={this.props.refresh}/>;
});
+ let className = classNames('data zebra', { 'new-loading': !this.props.ready });
return (
- <table id="permission-templates" className="data zebra">
+ <table id="permission-templates" className={className}>
<PermissionsHeader permissions={this.props.permissions}/>
<tbody>{permissionTemplates}</tbody>
</table>
},
getInitialState() {
- return { permissions: [], projects: [], total: 0 };
+ return { ready: false, permissions: [], projects: [], total: 0 };
},
componentDidMount() {
if (this.props.componentId) {
data = { projectId: this.props.componentId };
}
- $.get(url, data).done(r => {
- let permissions = this.sortPermissions(r.permissions);
- let projects = this.mergePermissionsToProjects(r.projects, permissions);
- if (page > 1) {
- projects = [].concat(this.state.projects, projects);
- }
- this.setState({
- projects: projects,
- permissions: permissions,
- total: r.paging.total,
- page: r.paging.pageIndex,
- query: query
+ this.setState({ ready: false }, () => {
+ $.get(url, data).done(r => {
+ let permissions = this.sortPermissions(r.permissions);
+ let projects = this.mergePermissionsToProjects(r.projects, permissions);
+ if (page > 1) {
+ projects = [].concat(this.state.projects, projects);
+ }
+ this.setState({
+ ready: true,
+ projects: projects,
+ permissions: permissions,
+ total: r.paging.total,
+ page: r.paging.pageIndex,
+ query: query
+ });
});
});
},
);
},
+ renderSpinner () {
+ if (this.state.ready) {
+ return null;
+ }
+ return <i className="spinner"/>;
+ },
+
render() {
return (
<div className="page">
<header id="project-permissions-header" className="page-header">
<h1 className="page-title">{window.t('roles.page')}</h1>
+ {this.renderSpinner()}
<div className="page-actions">
{this.renderBulkApplyButton()}
</div>
search={this.search}/>
<Permissions
+ ready={this.state.ready}
projects={this.state.projects}
permissions={this.state.permissions}
permissionTemplates={this.props.permissionTemplates}
refresh={this.refresh}/>
<PermissionsFooter {...this.props}
+ ready={this.state.ready}
count={this.state.projects.length}
total={this.state.total}
loadMore={this.loadMore}/>
+import classNames from 'classnames';
import React from 'react';
+
export default React.createClass({
propTypes:{
count: React.PropTypes.number.isRequired,
}
let hasMore = this.props.total > this.props.count;
let loadMoreLink = <a onClick={this.props.loadMore} className="spacer-left" href="#">show more</a>;
+ let className = classNames('spacer-top note text-center', { 'new-loading': !this.props.ready });
return (
- <footer className="spacer-top note text-center">
+ <footer className={className}>
{this.props.count}/{this.props.total} shown
{hasMore ? loadMoreLink : null}
</footer>
+import classNames from 'classnames';
import React from 'react';
+
import PermissionsHeader from './permissions-header';
import Project from './project';
+
export default React.createClass({
propTypes:{
projects: React.PropTypes.arrayOf(React.PropTypes.object).isRequired,
permissionTemplates={this.props.permissionTemplates}
refresh={this.props.refresh}/>;
});
+ let className = classNames('data zebra', { 'new-loading': !this.props.ready });
return (
- <table id="projects" className="data zebra">
+ <table id="projects" className={className}>
<PermissionsHeader permissions={this.props.permissions}/>
<tbody>{projects}</tbody>
</table>
import ModalForm from '../../components/common/modal-form';
-import {createProject} from '../../api/components';
+import { createProject } from '../../api/components';
import Template from './templates/projects-create-form.hbs';
+
export default ModalForm.extend({
template: Template,
key: this.$('#create-project-key').val()
};
this.disableForm();
- return createProject({
- data,
- statusCode: {
- // do not show global error
- 400: null
- }
- }).done(() => {
- if (this.options.refresh) {
- this.options.refresh();
- }
- this.destroy();
- }).fail((jqXHR) => {
- this.enableForm();
- this.showErrors([{ msg: jqXHR.responseJSON.err_msg }]);
- });
+ return createProject(data)
+ .then(() => {
+ if (this.options.refresh) {
+ this.options.refresh();
+ }
+ this.destroy();
+ })
+ .catch(error => {
+ this.enableForm();
+ if (error.response.status === 400) {
+ error.response.json().then(obj => this.showErrors([{ msg: obj.err_msg }]));
+ }
+ });
}
});
getInitialState() {
return {
+ ready: false,
projects: [],
total: 0,
page: 1,
requestGhosts() {
let data = this.getFilters();
- getGhosts(data).done(r => {
+ getGhosts(data).then(r => {
let projects = r.projects.map(project => {
return _.extend(project, { id: project.uuid, qualifier: 'TRK' });
});
if (this.state.page > 1) {
projects = [].concat(this.state.projects, projects);
}
- this.setState({ projects: projects, total: r.total });
+ this.setState({ ready: true, projects: projects, total: r.total });
});
},
requestProvisioned() {
let data = this.getFilters();
- getProvisioned(data).done(r => {
+ getProvisioned(data).then(r => {
let projects = r.projects.map(project => {
return _.extend(project, { id: project.uuid, qualifier: 'TRK' });
});
if (this.state.page > 1) {
projects = [].concat(this.state.projects, projects);
}
- this.setState({ projects: projects, total: r.total });
+ this.setState({ ready: true, projects: projects, total: r.total });
});
},
requestAllProjects() {
let data = this.getFilters();
data.qualifiers = this.state.qualifiers;
- getComponents(data).done(r => {
+ getComponents(data).then(r => {
let projects = r.components;
if (this.state.page > 1) {
projects = [].concat(this.state.projects, projects);
}
- this.setState({ projects: projects, total: r.paging.total });
+ this.setState({ ready: true, projects: projects, total: r.paging.total });
});
},
loadMore() {
- this.setState({ page: this.state.page + 1 }, this.requestProjects);
+ this.setState({ ready: false, page: this.state.page + 1 }, this.requestProjects);
},
onSearch(query) {
this.setState({
+ ready: false,
page: 1,
query,
selection: []
onTypeChanged(newType) {
this.setState({
+ ready: false,
page: 1,
query: '',
type: newType,
onQualifierChanged(newQualifier) {
this.setState({
+ ready: false,
page: 1,
query: '',
type: TYPE.ALL,
deleteProjects() {
let ids = this.state.selection.join(',');
- deleteComponents({ ids }).done(() => {
+ deleteComponents({ ids }).then(() => {
this.setState({ page: 1, selection: [] }, this.requestProjects);
});
},
deleteProjects={this.deleteProjects}/>
<Projects
+ ready={this.state.ready}
projects={this.state.projects}
refresh={this.requestProjects}
selection={this.state.selection}
onProjectDeselected={this.onProjectDeselected}/>
<ListFooter
+ ready={this.state.ready}
count={this.state.projects.length}
total={this.state.total}
loadMore={this.loadMore}/>
+import classNames from 'classnames';
import React from 'react';
import { getComponentUrl } from '../../helpers/urls';
import Checkbox from '../../components/shared/checkbox';
},
render() {
+ let className = classNames('data', 'zebra', { 'new-loading': !this.props.ready });
return (
- <table className="data zebra">
+ <table className={className}>
<tbody>{this.props.projects.map(this.renderProject)}</tbody>
</table>
);
return <Checkbox onCheck={this.onCheck} initiallyChecked={isChecked} thirdState={thirdState}/>;
},
+ renderSpinner() {
+ return <i className="spinner"/>;
+ },
+
onCheck(checked) {
if (checked) {
this.props.onAllSelected();
},
renderGhostsDescription () {
- if (this.props.type !== TYPE.GHOSTS) {
+ if (this.props.type !== TYPE.GHOSTS || !this.props.ready) {
return null;
}
return <div className="spacer-top alert alert-info">{window.t('bulk_deletion.ghosts.description')}</div>;
<tbody>
<tr>
<td className="thin text-middle">
- {this.renderCheckbox()}
+ {this.props.ready ? this.renderCheckbox() : this.renderSpinner()}
</td>
{this.renderQualifierFilter()}
<td className="thin nowrap text-middle">
export default Marionette.ItemView.extend({
template: Template,
+ collectionEvents: {
+ 'request': 'showSpinner',
+ 'sync': 'hideSpinner'
+ },
+
events: {
'click #users-create': 'onCreateClick'
},
+ showSpinner: function () {
+ this.$('.spinner').removeClass('hidden');
+ },
+
+ hideSpinner: function () {
+ this.$('.spinner').addClass('hidden');
+ },
+
onCreateClick: function (e) {
e.preventDefault();
this.createUser();
export default Marionette.CollectionView.extend({
tagName: 'ul',
- childView: ListItemView
+ childView: ListItemView,
+
+ collectionEvents: {
+ 'request': 'showLoading',
+ 'sync': 'hideLoading'
+ },
+
+ showLoading: function () {
+ this.$el.addClass('new-loading');
+ },
+
+ hideLoading: function () {
+ this.$el.removeClass('new-loading');
+ }
});
<header class="page-header">
<h1 class="page-title">{{t 'users.page'}}</h1>
+ <i class="spinner hidden"></i>
<div class="page-actions">
<div class="button-group">
<button id="users-create">Create User</button>
+import classNames from 'classnames';
import React from 'react';
+
export default React.createClass({
propTypes: {
count: React.PropTypes.number.isRequired,
return typeof this.props.loadMore === 'function';
},
- loadMoreProxy(e) {
+ handleLoadMore(e) {
e.preventDefault();
if (this.canLoadMore()) {
this.props.loadMore();
}
},
+ renderLoading() {
+ return <footer className="spacer-top note text-center">
+ {window.t('loading')}
+ </footer>;
+ },
+
render() {
let hasMore = this.props.total > this.props.count,
- loadMoreLink = <a onClick={this.loadMoreProxy} className="spacer-left" href="#">show more</a>;
+ loadMoreLink = <a onClick={this.handleLoadMore} className="spacer-left" href="#">show more</a>;
+ let className = classNames('spacer-top note text-center', { 'new-loading': !this.props.ready });
return (
- <footer className="spacer-top note text-center">
+ <footer className={className}>
{this.props.count}/{this.props.total} shown
{this.canLoadMore() && hasMore ? loadMoreLink : null}
</footer>
.submit()
.then(checkStatus);
}
+
+
+/**
+ * Delay promise for testing purposes
+ * @param response
+ * @returns {Promise}
+ */
+export function delay (response) {
+ return new Promise(resolve => setTimeout(() => resolve(response), 3000));
+}
.page-header {
.clearfix;
margin-bottom: 10px;
+
+ .spinner {
+ position: relative;
+ top: 3px;
+ margin-left: 8px;
+ }
}
.page-title {
justify-content: space-between !important;
}
+.new-loading {
+ opacity: 0.5;
+ transition: opacity 0.5s ease;
+}
+
// Background Color