api.js refactor using classes (#250)

Refactor api.js into an api module where endpoint specific models can be imported and used in components.
This commit is contained in:
Michael Abashian
2019-06-07 15:48:09 -04:00
committed by GitHub
parent a8c51670af
commit 2ae93261d1
51 changed files with 839 additions and 727 deletions

View File

@@ -15,6 +15,7 @@ import styled from 'styled-components';
import { RootDialog } from './contexts/RootDialog';
import { withNetwork } from './contexts/Network';
import { Config } from './contexts/Config';
import { RootAPI } from './api';
import AlertModal from './components/AlertModal';
import About from './components/About';
@@ -56,9 +57,9 @@ class App extends Component {
}
async onLogout () {
const { api, handleHttpError } = this.props;
const { handleHttpError } = this.props;
try {
await api.logout();
await RootAPI.logout();
window.location.replace('/#/login');
} catch (err) {
handleHttpError(err);

View File

@@ -1,182 +0,0 @@
const API_ROOT = '/api/';
const API_LOGIN = `${API_ROOT}login/`;
const API_LOGOUT = `${API_ROOT}logout/`;
const API_V2 = `${API_ROOT}v2/`;
const API_CONFIG = `${API_V2}config/`;
const API_ME = `${API_V2}me/`;
const API_ORGANIZATIONS = `${API_V2}organizations/`;
const API_INSTANCE_GROUPS = `${API_V2}instance_groups/`;
const API_USERS = `${API_V2}users/`;
const API_TEAMS = `${API_V2}teams/`;
const LOGIN_CONTENT_TYPE = 'application/x-www-form-urlencoded';
class APIClient {
static getCookie () {
return document.cookie;
}
constructor (httpAdapter) {
this.http = httpAdapter;
}
isAuthenticated () {
const cookie = this.constructor.getCookie();
const parsed = (`; ${cookie}`).split('; userLoggedIn=');
let authenticated = false;
if (parsed.length === 2) {
authenticated = parsed.pop().split(';').shift() === 'true';
}
return authenticated;
}
async login (username, password, redirect = API_CONFIG) {
const un = encodeURIComponent(username);
const pw = encodeURIComponent(password);
const next = encodeURIComponent(redirect);
const data = `username=${un}&password=${pw}&next=${next}`;
const headers = { 'Content-Type': LOGIN_CONTENT_TYPE };
await this.http.get(API_LOGIN, { headers });
const response = await this.http.post(API_LOGIN, data, { headers });
return response;
}
logout () {
return this.http.get(API_LOGOUT);
}
getRoot () {
return this.http.get(API_ROOT);
}
getConfig () {
return this.http.get(API_CONFIG);
}
getMe () {
return this.http.get(API_ME);
}
destroyOrganization (id) {
const endpoint = `${API_ORGANIZATIONS}${id}/`;
return (this.http.delete(endpoint));
}
getOrganizations (params = {}) {
return this.http.get(API_ORGANIZATIONS, { params });
}
createOrganization (data) {
return this.http.post(API_ORGANIZATIONS, data);
}
optionsOrganizations () {
return this.http.options(API_ORGANIZATIONS);
}
getOrganizationAccessList (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/access_list/`;
return this.http.get(endpoint, { params });
}
readOrganizationTeamsList (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/teams/`;
return this.http.get(endpoint, { params });
}
getOrganizationDetails (id) {
const endpoint = `${API_ORGANIZATIONS}${id}/`;
return this.http.get(endpoint);
}
updateOrganizationDetails (id, data) {
const endpoint = `${API_ORGANIZATIONS}${id}/`;
return this.http.patch(endpoint, data);
}
getOrganizationInstanceGroups (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/instance_groups/`;
return this.http.get(endpoint, { params });
}
getOrganizationNotifications (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates/`;
return this.http.get(endpoint, { params });
}
getOrganizationNotificationSuccess (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_success/`;
return this.http.get(endpoint, { params });
}
getOrganizationNotificationError (id, params = {}) {
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_error/`;
return this.http.get(endpoint, { params });
}
createOrganizationNotificationSuccess (id, data) {
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_success/`;
return this.http.post(endpoint, data);
}
createOrganizationNotificationError (id, data) {
const endpoint = `${API_ORGANIZATIONS}${id}/notification_templates_error/`;
return this.http.post(endpoint, data);
}
getInstanceGroups (params) {
return this.http.get(API_INSTANCE_GROUPS, { params });
}
associateInstanceGroup (url, id) {
return this.http.post(url, { id });
}
disassociateTeamRole (teamId, roleId) {
const url = `/api/v2/teams/${teamId}/roles/`;
return this.disassociate(url, roleId);
}
disassociateUserRole (accessRecordId, roleId) {
const url = `/api/v2/users/${accessRecordId}/roles/`;
return this.disassociate(url, roleId);
}
disassociate (url, id) {
return this.http.post(url, { id, disassociate: true });
}
readUsers (params) {
return this.http.get(API_USERS, { params });
}
readTeams (params) {
return this.http.get(API_TEAMS, { params });
}
createUserRole (userId, roleId) {
return this.http.post(`${API_USERS}${userId}/roles/`, { id: roleId });
}
createTeamRole (teamId, roleId) {
return this.http.post(`${API_TEAMS}${teamId}/roles/`, { id: roleId });
}
}
export default APIClient;

43
src/api/Base.js Normal file
View File

@@ -0,0 +1,43 @@
import axios from 'axios';
const defaultHttp = axios.create({
xsrfCookieName: 'csrftoken',
xsrfHeaderName: 'X-CSRFToken'
});
class Base {
constructor (http = defaultHttp, baseURL) {
this.http = http;
this.baseUrl = baseURL;
}
create (data) {
return this.http.post(this.baseUrl, data);
}
destroy (id) {
return this.http.delete(`${this.baseUrl}${id}/`);
}
read (params = {}) {
return this.http.get(this.baseUrl, { params });
}
readDetail (id) {
return this.http.get(`${this.baseUrl}${id}/`);
}
readOptions () {
return this.http.options(this.baseUrl);
}
replace (id, data) {
return this.http.put(`${this.baseUrl}${id}/`, data);
}
update (id, data) {
return this.http.patch(`${this.baseUrl}${id}/`, data);
}
}
export default Base;

25
src/api/index.js Normal file
View File

@@ -0,0 +1,25 @@
import Config from './models/Config';
import InstanceGroups from './models/InstanceGroups';
import Me from './models/Me';
import Organizations from './models/Organizations';
import Root from './models/Root';
import Teams from './models/Teams';
import Users from './models/Users';
const ConfigAPI = new Config();
const InstanceGroupsAPI = new InstanceGroups();
const MeAPI = new Me();
const OrganizationsAPI = new Organizations();
const RootAPI = new Root();
const TeamsAPI = new Teams();
const UsersAPI = new Users();
export {
ConfigAPI,
InstanceGroupsAPI,
MeAPI,
OrganizationsAPI,
RootAPI,
TeamsAPI,
UsersAPI
};

View File

@@ -0,0 +1,15 @@
const InstanceGroupsMixin = (parent) => class extends parent {
readInstanceGroups (resourceId, params = {}) {
return this.http.get(`${this.baseUrl}${resourceId}/instance_groups/`, { params });
}
associateInstanceGroup (resourceId, instanceGroupId) {
return this.http.post(`${this.baseUrl}${resourceId}/instance_groups/`, { id: instanceGroupId });
}
disassociateInstanceGroup (resourceId, instanceGroupId) {
return this.http.post(`${this.baseUrl}${resourceId}/instance_groups/`, { id: instanceGroupId, disassociate: true });
}
};
export default InstanceGroupsMixin;

View File

@@ -0,0 +1,31 @@
const NotificationsMixin = (parent) => class extends parent {
readNotificationTemplates (id, params = {}) {
return this.http.get(`${this.baseUrl}${id}/notification_templates/`, { params });
}
readNotificationTemplatesSuccess (id, params = {}) {
return this.http.get(`${this.baseUrl}${id}/notification_templates_success/`, { params });
}
readNotificationTemplatesError (id, params = {}) {
return this.http.get(`${this.baseUrl}${id}/notification_templates_error/`, { params });
}
associateNotificationTemplatesSuccess (resourceId, notificationId) {
return this.http.post(`${this.baseUrl}${resourceId}/notification_templates_success/`, { id: notificationId });
}
disassociateNotificationTemplatesSuccess (resourceId, notificationId) {
return this.http.post(`${this.baseUrl}${resourceId}/notification_templates_success/`, { id: notificationId, disassociate: true });
}
associateNotificationTemplatesError (resourceId, notificationId) {
return this.http.post(`${this.baseUrl}${resourceId}/notification_templates_error/`, { id: notificationId });
}
disassociateNotificationTemplatesError (resourceId, notificationId) {
return this.http.post(`${this.baseUrl}${resourceId}/notification_templates_error/`, { id: notificationId, disassociate: true });
}
};
export default NotificationsMixin;

10
src/api/models/Config.js Normal file
View File

@@ -0,0 +1,10 @@
import Base from '../Base';
class Config extends Base {
constructor (http) {
super(http);
this.baseUrl = '/api/v2/config/';
}
}
export default Config;

View File

@@ -0,0 +1,10 @@
import Base from '../Base';
class InstanceGroups extends Base {
constructor (http) {
super(http);
this.baseUrl = '/api/v2/instance_groups/';
}
}
export default InstanceGroups;

10
src/api/models/Me.js Normal file
View File

@@ -0,0 +1,10 @@
import Base from '../Base';
class Me extends Base {
constructor (http) {
super(http);
this.baseUrl = '/api/v2/me/';
}
}
export default Me;

View File

@@ -0,0 +1,20 @@
import Base from '../Base';
import NotificationsMixin from '../mixins/Notifications.mixin';
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) {
constructor (http) {
super(http);
this.baseUrl = '/api/v2/organizations/';
}
readAccessList (id, params = {}) {
return this.http.get(`${this.baseUrl}${id}/access_list/`, { params });
}
readTeams (id, params = {}) {
return this.http.get(`${this.baseUrl}${id}/teams/`, { params });
}
}
export default Organizations;

30
src/api/models/Root.js Normal file
View File

@@ -0,0 +1,30 @@
import Base from '../Base';
class Root extends Base {
constructor (http) {
super(http);
this.baseUrl = '/api/';
this.redirectURL = '/api/v2/config/';
}
async login (username, password, redirect = this.redirectURL) {
const loginUrl = `${this.baseUrl}login/`;
const un = encodeURIComponent(username);
const pw = encodeURIComponent(password);
const next = encodeURIComponent(redirect);
const data = `username=${un}&password=${pw}&next=${next}`;
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
await this.http.get(loginUrl, { headers });
const response = await this.http.post(loginUrl, data, { headers });
return response;
}
logout () {
return this.http.get(`${this.baseUrl}logout/`);
}
}
export default Root;

18
src/api/models/Teams.js Normal file
View File

@@ -0,0 +1,18 @@
import Base from '../Base';
class Teams extends Base {
constructor (http) {
super(http);
this.baseUrl = '/api/v2/teams/';
}
associateRole (teamId, roleId) {
return this.http.post(`${this.baseUrl}${teamId}/roles/`, { id: roleId });
}
disassociateRole (teamId, roleId) {
return this.http.post(`${this.baseUrl}${teamId}/roles/`, { id: roleId, disassociate: true });
}
}
export default Teams;

18
src/api/models/Users.js Normal file
View File

@@ -0,0 +1,18 @@
import Base from '../Base';
class Users extends Base {
constructor (http) {
super(http);
this.baseUrl = '/api/v2/users/';
}
associateRole (userId, roleId) {
return this.http.post(`${this.baseUrl}${userId}/roles/`, { id: roleId });
}
disassociateRole (userId, roleId) {
return this.http.post(`${this.baseUrl}${userId}/roles/`, { id: roleId, disassociate: true });
}
}
export default Users;

View File

@@ -7,6 +7,13 @@ import { withNetwork } from '../../contexts/Network';
import SelectResourceStep from './SelectResourceStep';
import SelectRoleStep from './SelectRoleStep';
import SelectableCard from './SelectableCard';
import { TeamsAPI, UsersAPI } from '../../api';
const readUsers = async (queryParams) => UsersAPI.read(
Object.assign(queryParams, { is_superuser: false })
);
const readTeams = async (queryParams) => TeamsAPI.read(queryParams);
class AddResourceRole extends React.Component {
constructor (props) {
@@ -24,8 +31,6 @@ class AddResourceRole extends React.Component {
this.handleRoleCheckboxClick = this.handleRoleCheckboxClick.bind(this);
this.handleWizardNext = this.handleWizardNext.bind(this);
this.handleWizardSave = this.handleWizardSave.bind(this);
this.readTeams = this.readTeams.bind(this);
this.readUsers = this.readUsers.bind(this);
}
handleResourceCheckboxClick (user) {
@@ -76,8 +81,7 @@ class AddResourceRole extends React.Component {
async handleWizardSave () {
const {
onSave,
api
onSave
} = this.props;
const {
selectedResourceRows,
@@ -92,11 +96,11 @@ class AddResourceRole extends React.Component {
for (let j = 0; j < selectedRoleRows.length; j++) {
if (selectedResource === 'users') {
roleRequests.push(
api.createUserRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
UsersAPI.associateRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
);
} else if (selectedResource === 'teams') {
roleRequests.push(
api.createTeamRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
TeamsAPI.associateRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
);
}
}
@@ -109,16 +113,6 @@ class AddResourceRole extends React.Component {
}
}
async readUsers (queryParams) {
const { api } = this.props;
return api.readUsers(Object.assign(queryParams, { is_superuser: false }));
}
async readTeams (queryParams) {
const { api } = this.props;
return api.readTeams(queryParams);
}
render () {
const {
selectedResource,
@@ -183,7 +177,7 @@ class AddResourceRole extends React.Component {
columns={userColumns}
displayKey="username"
onRowClick={this.handleResourceCheckboxClick}
onSearch={this.readUsers}
onSearch={readUsers}
selectedLabel={i18n._(t`Selected`)}
selectedResourceRows={selectedResourceRows}
sortedColumnKey="username"
@@ -194,7 +188,7 @@ class AddResourceRole extends React.Component {
<SelectResourceStep
columns={teamColumns}
onRowClick={this.handleResourceCheckboxClick}
onSearch={this.readTeams}
onSearch={readTeams}
selectedLabel={i18n._(t`Selected`)}
selectedResourceRows={selectedResourceRows}
itemName="team"

View File

@@ -2,6 +2,8 @@ import React, { Component } from 'react';
import { withNetwork } from './Network';
import { ConfigAPI, MeAPI, RootAPI } from '../api';
const ConfigContext = React.createContext({});
class Provider extends Component {
@@ -46,13 +48,13 @@ class Provider extends Component {
};
async fetchMe () {
const { api, handleHttpError } = this.props;
const { handleHttpError } = this.props;
try {
const {
data: {
results: [me]
}
} = await api.getMe();
} = await MeAPI.read();
this.setState(prevState => ({
value: {
...prevState.value,
@@ -75,13 +77,13 @@ class Provider extends Component {
}
async fetchConfig () {
const { api, handleHttpError } = this.props;
const { handleHttpError } = this.props;
try {
const [configRes, rootRes, meRes] = await Promise.all([
api.getConfig(),
api.getRoot(),
api.getMe()
ConfigAPI.read(),
RootAPI.read(),
MeAPI.read()
]);
this.setState({
value: {

View File

@@ -1,5 +1,4 @@
import axios from 'axios';
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
@@ -9,8 +8,6 @@ import { t } from '@lingui/macro';
import { withRootDialog } from './RootDialog';
import APIClient from '../api';
const NetworkContext = React.createContext({});
class Provider extends Component {
@@ -19,7 +16,6 @@ class Provider extends Component {
this.state = {
value: {
api: new APIClient(axios.create({ xsrfCookieName: 'csrftoken', xsrfHeaderName: 'X-CSRFToken' })),
handleHttpError: err => {
if (err.response.status === 401) {
this.handle401();

View File

@@ -9,6 +9,7 @@ import {
import { withRootDialog } from '../contexts/RootDialog';
import { withNetwork } from '../contexts/Network';
import { RootAPI } from '../api';
import towerLogo from '../../images/tower-logo-header.svg';
@@ -39,7 +40,7 @@ class AWXLogin extends Component {
async onLoginButtonClick (event) {
const { username, password, isLoading } = this.state;
const { api, handleHttpError, clearRootDialogMessage, fetchMe, updateConfig } = this.props;
const { handleHttpError, clearRootDialogMessage, fetchMe, updateConfig } = this.props;
event.preventDefault();
@@ -51,7 +52,7 @@ class AWXLogin extends Component {
this.setState({ isLoading: true });
try {
const { data } = await api.login(username, password);
const { data } = await RootAPI.login(username, password);
updateConfig(data);
await fetchMe();
this.setState({ isAuthenticated: true, isLoading: false });

View File

@@ -9,19 +9,11 @@ import Lookup from '../../../components/Lookup';
import { withNetwork } from '../../../contexts/Network';
import { InstanceGroupsAPI } from '../../../api';
const getInstanceGroups = async (params) => InstanceGroupsAPI.read(params);
class InstanceGroupsLookup extends React.Component {
constructor (props) {
super(props);
this.getInstanceGroups = this.getInstanceGroups.bind(this);
}
async getInstanceGroups (params) {
const { api } = this.props;
const data = await api.getInstanceGroups(params);
return data;
}
render () {
const { value, tooltip, onChange, i18n } = this.props;
@@ -51,7 +43,7 @@ class InstanceGroupsLookup extends React.Component {
name="instanceGroups"
value={value}
onLookupSave={onChange}
getItems={this.getInstanceGroups}
getItems={getInstanceGroups}
columns={[
{ name: i18n._(t`Name`), key: 'name', isSortable: true },
{ name: i18n._(t`Modified`), key: 'modified', isSortable: false, isNumeric: true },

View File

@@ -17,6 +17,7 @@ import FormActionGroup from '../../../components/FormActionGroup/FormActionGroup
import AnsibleSelect from '../../../components/AnsibleSelect';
import InstanceGroupsLookup from './InstanceGroupsLookup';
import { required } from '../../../util/validators';
import { OrganizationsAPI } from '../../../api';
class OrganizationForm extends Component {
constructor (props) {
@@ -52,10 +53,9 @@ class OrganizationForm extends Component {
async getRelatedInstanceGroups () {
const {
api,
organization: { id }
} = this.props;
const { data } = await api.getOrganizationInstanceGroups(id);
const { data } = await OrganizationsAPI.readInstanceGroups(id);
return data.results;
}

View File

@@ -12,6 +12,7 @@ import OrganizationEdit from './OrganizationEdit';
import OrganizationNotifications from './OrganizationNotifications';
import OrganizationTeams from './OrganizationTeams';
import RoutedTabs from '../../../../components/Tabs/RoutedTabs';
import { OrganizationsAPI } from '../../../../api';
class Organization extends Component {
constructor (props) {
@@ -45,22 +46,21 @@ class Organization extends Component {
const {
match,
setBreadcrumb,
api,
handleHttpError
} = this.props;
try {
const [{ data }, notifAdminRest, auditorRes, adminRes] = await Promise.all([
api.getOrganizationDetails(parseInt(match.params.id, 10)),
api.getOrganizations({
OrganizationsAPI.readDetail(parseInt(match.params.id, 10)),
OrganizationsAPI.read({
role_level: 'notification_admin_role',
page_size: 1
}),
api.getOrganizations({
OrganizationsAPI.read({
role_level: 'auditor_role',
id: parseInt(match.params.id, 10)
}),
api.getOrganizations({
OrganizationsAPI.read({
role_level: 'admin_role',
id: parseInt(match.params.id, 10)
})
@@ -82,12 +82,11 @@ class Organization extends Component {
const {
match,
setBreadcrumb,
api,
handleHttpError
} = this.props;
try {
const { data } = await api.getOrganizationDetails(parseInt(match.params.id, 10));
const { data } = await OrganizationsAPI.readDetail(parseInt(match.params.id, 10));
setBreadcrumb(data);
this.setState({ organization: data, loading: false });
} catch (error) {

View File

@@ -10,6 +10,7 @@ import AddResourceRole from '../../../../components/AddRole/AddResourceRole';
import { withNetwork } from '../../../../contexts/Network';
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
import { Organization } from '../../../../types';
import { OrganizationsAPI, TeamsAPI, UsersAPI } from '../../../../api';
const QS_CONFIG = getQSConfig('access', {
page: 1,
@@ -56,10 +57,10 @@ class OrganizationAccess extends React.Component {
}
async readOrgAccessList () {
const { organization, api, handleHttpError, location } = this.props;
const { organization, handleHttpError, location } = this.props;
this.setState({ isLoading: true });
try {
const { data } = await api.getOrganizationAccessList(
const { data } = await OrganizationsAPI.readAccessList(
organization.id,
parseNamespacedQueryString(QS_CONFIG, location.search)
);
@@ -92,7 +93,7 @@ class OrganizationAccess extends React.Component {
}
async removeRole () {
const { api, handleHttpError } = this.props;
const { handleHttpError } = this.props;
const { roleToDelete: role, roleToDeleteAccessRecord: accessRecord } = this.state;
if (!role || !accessRecord) {
return;
@@ -101,9 +102,9 @@ class OrganizationAccess extends React.Component {
this.setState({ isLoading: true });
try {
if (type === 'teams') {
await api.disassociateTeamRole(role.team_id, role.id);
await TeamsAPI.disassociateRole(role.team_id, role.id);
} else {
await api.disassociateUserRole(accessRecord.id, role.id);
await UsersAPI.disassociateRole(accessRecord.id, role.id);
}
this.setState({
isLoading: false,

View File

@@ -7,6 +7,7 @@ import styled from 'styled-components';
import { DetailList, Detail } from '../../../../components/DetailList';
import { withNetwork } from '../../../../contexts/Network';
import { ChipGroup, Chip } from '../../../../components/Chip';
import { OrganizationsAPI } from '../../../../api';
const CardBody = styled(PFCardBody)`
padding-top: 20px;
@@ -29,14 +30,13 @@ class OrganizationDetail extends Component {
async loadInstanceGroups () {
const {
api,
handleHttpError,
match
} = this.props;
try {
const {
data
} = await api.getOrganizationInstanceGroups(match.params.id);
} = await OrganizationsAPI.readInstanceGroups(match.params.id);
this.setState({
instanceGroups: [...data.results]
});

View File

@@ -4,6 +4,7 @@ import { withRouter } from 'react-router-dom';
import { CardBody } from '@patternfly/react-core';
import OrganizationForm from '../../components/OrganizationForm';
import { withNetwork } from '../../../../contexts/Network';
import { OrganizationsAPI } from '../../../../api';
class OrganizationEdit extends Component {
constructor (props) {
@@ -20,9 +21,9 @@ class OrganizationEdit extends Component {
}
async handleSubmit (values, groupsToAssociate, groupsToDisassociate) {
const { api, organization, handleHttpError } = this.props;
const { organization, handleHttpError } = this.props;
try {
await api.updateOrganizationDetails(organization.id, values);
await OrganizationsAPI.update(organization.id, values);
await this.submitInstanceGroups(groupsToAssociate, groupsToDisassociate);
this.handleSuccess();
} catch (err) {
@@ -41,12 +42,17 @@ class OrganizationEdit extends Component {
}
async submitInstanceGroups (groupsToAssociate, groupsToDisassociate) {
const { api, organization, handleHttpError } = this.props;
const url = organization.related.instance_groups;
const { organization, handleHttpError } = this.props;
try {
await Promise.all(groupsToAssociate.map(id => api.associateInstanceGroup(url, id)));
await Promise.all(groupsToDisassociate.map(id => api.disassociate(url, id)));
await Promise.all(
groupsToAssociate.map(id => OrganizationsAPI.associateInstanceGroup(organization.id, id))
);
await Promise.all(
groupsToDisassociate.map(
id => OrganizationsAPI.disassociateInstanceGroup(organization.id, id)
)
);
} catch (err) {
handleHttpError(err) || this.setState({ error: err });
}

View File

@@ -5,6 +5,7 @@ import { withNetwork } from '../../../../contexts/Network';
import PaginatedDataList from '../../../../components/PaginatedDataList';
import NotificationListItem from '../../../../components/NotificationsList/NotificationListItem';
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
import { OrganizationsAPI } from '../../../../api';
const QS_CONFIG = getQSConfig('notification', {
page: 1,
@@ -49,11 +50,11 @@ class OrganizationNotifications extends Component {
}
async readNotifications () {
const { id, api, handleHttpError, location } = this.props;
const { id, handleHttpError, location } = this.props;
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
this.setState({ isLoading: true });
try {
const { data } = await api.getOrganizationNotifications(id, params);
const { data } = await OrganizationsAPI.readNotificationTemplates(id, params);
this.setState(
{
itemCount: data.count || 0,
@@ -72,21 +73,22 @@ class OrganizationNotifications extends Component {
}
async readSuccessesAndErrors () {
const { api, handleHttpError, id } = this.props;
const { handleHttpError, id } = this.props;
const { notifications } = this.state;
if (!notifications.length) {
return;
}
const ids = notifications.map(n => n.id).join(',');
try {
const successTemplatesPromise = api.getOrganizationNotificationSuccess(
const successTemplatesPromise = OrganizationsAPI.readNotificationTemplatesSuccess(
id,
{ id__in: ids }
);
const errorTemplatesPromise = api.getOrganizationNotificationError(
const errorTemplatesPromise = OrganizationsAPI.readNotificationTemplatesError(
id,
{ id__in: ids }
);
const { data: successTemplates } = await successTemplatesPromise;
const { data: errorTemplates } = await errorTemplatesPromise;
@@ -104,53 +106,65 @@ class OrganizationNotifications extends Component {
toggleNotification = (notificationId, isCurrentlyOn, status) => {
if (status === 'success') {
this.createSuccess(notificationId, isCurrentlyOn);
if (isCurrentlyOn) {
this.disassociateSuccess(notificationId);
} else {
this.associateSuccess(notificationId);
}
} else if (status === 'error') {
this.createError(notificationId, isCurrentlyOn);
if (isCurrentlyOn) {
this.disassociateError(notificationId);
} else {
this.associateError(notificationId);
}
}
};
async createSuccess (notificationId, isCurrentlyOn) {
const { id, api, handleHttpError } = this.props;
const postParams = { id: notificationId };
if (isCurrentlyOn) {
postParams.disassociate = true;
}
async associateSuccess (notificationId) {
const { id, handleHttpError } = this.props;
try {
await api.createOrganizationNotificationSuccess(id, postParams);
if (isCurrentlyOn) {
this.setState((prevState) => ({
successTemplateIds: prevState.successTemplateIds
.filter((templateId) => templateId !== notificationId)
}));
} else {
this.setState(prevState => ({
successTemplateIds: [...prevState.successTemplateIds, notificationId]
}));
}
await OrganizationsAPI.associateNotificationTemplatesSuccess(id, notificationId);
this.setState(prevState => ({
successTemplateIds: [...prevState.successTemplateIds, notificationId]
}));
} catch (err) {
handleHttpError(err) || this.setState({ error: true });
}
}
async createError (notificationId, isCurrentlyOn) {
const { id, api, handleHttpError } = this.props;
const postParams = { id: notificationId };
if (isCurrentlyOn) {
postParams.disassociate = true;
}
async disassociateSuccess (notificationId) {
const { id, handleHttpError } = this.props;
try {
await api.createOrganizationNotificationError(id, postParams);
if (isCurrentlyOn) {
this.setState((prevState) => ({
errorTemplateIds: prevState.errorTemplateIds
.filter((templateId) => templateId !== notificationId)
}));
} else {
this.setState(prevState => ({
errorTemplateIds: [...prevState.errorTemplateIds, notificationId]
}));
}
await OrganizationsAPI.disassociateNotificationTemplatesSuccess(id, notificationId);
this.setState((prevState) => ({
successTemplateIds: prevState.successTemplateIds
.filter((templateId) => templateId !== notificationId)
}));
} catch (err) {
handleHttpError(err) || this.setState({ error: true });
}
}
async associateError (notificationId) {
const { id, handleHttpError } = this.props;
try {
await OrganizationsAPI.associateNotificationTemplatesError(id, notificationId);
this.setState(prevState => ({
errorTemplateIds: [...prevState.errorTemplateIds, notificationId]
}));
} catch (err) {
handleHttpError(err) || this.setState({ error: true });
}
}
async disassociateError (notificationId) {
const { id, handleHttpError } = this.props;
try {
await OrganizationsAPI.disassociateNotificationTemplatesError(id, notificationId);
this.setState((prevState) => ({
errorTemplateIds: prevState.errorTemplateIds
.filter((templateId) => templateId !== notificationId)
}));
} catch (err) {
handleHttpError(err) || this.setState({ error: true });
}
@@ -205,13 +219,6 @@ OrganizationNotifications.propTypes = {
id: number.isRequired,
canToggleNotifications: bool.isRequired,
handleHttpError: func.isRequired,
api: shape({
getOrganizationNotifications: func.isRequired,
getOrganizationNotificationSuccess: func.isRequired,
getOrganizationNotificationError: func.isRequired,
createOrganizationNotificationSuccess: func.isRequired,
createOrganizationNotificationError: func.isRequired,
}).isRequired,
location: shape({
search: string.isRequired,
}).isRequired,

View File

@@ -4,6 +4,7 @@ import { withRouter } from 'react-router-dom';
import PaginatedDataList from '../../../../components/PaginatedDataList';
import { getQSConfig, parseNamespacedQueryString } from '../../../../util/qs';
import { withNetwork } from '../../../../contexts/Network';
import { OrganizationsAPI } from '../../../../api';
const QS_CONFIG = getQSConfig('team', {
page: 1,
@@ -38,13 +39,13 @@ class OrganizationTeams extends React.Component {
}
async readOrganizationTeamsList () {
const { id, api, handleHttpError, location } = this.props;
const { id, handleHttpError, location } = this.props;
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
this.setState({ isLoading: true, error: null });
try {
const {
data: { count = 0, results = [] },
} = await api.readOrganizationTeamsList(id, params);
} = await OrganizationsAPI.readTeams(id, params);
this.setState({
itemCount: count,
teams: results,

View File

@@ -14,6 +14,7 @@ import {
import { withNetwork } from '../../../contexts/Network';
import CardCloseButton from '../../../components/CardCloseButton';
import OrganizationForm from '../components/OrganizationForm';
import { OrganizationsAPI } from '../../../api';
class OrganizationAdd extends React.Component {
constructor (props) {
@@ -29,13 +30,12 @@ class OrganizationAdd extends React.Component {
}
async handleSubmit (values, groupsToAssociate) {
const { api, handleHttpError } = this.props;
const { handleHttpError } = this.props;
try {
const { data: response } = await api.createOrganization(values);
const instanceGroupsUrl = response.related.instance_groups;
const { data: response } = await OrganizationsAPI.create(values);
try {
await Promise.all(groupsToAssociate.map(id => api
.associateInstanceGroup(instanceGroupsUrl, id)));
await Promise.all(groupsToAssociate.map(id => OrganizationsAPI
.associateInstanceGroup(response.id, id)));
this.handleSuccess(response.id);
} catch (err) {
handleHttpError(err) || this.setState({ error: err });
@@ -83,10 +83,6 @@ class OrganizationAdd extends React.Component {
}
}
OrganizationAdd.propTypes = {
api: PropTypes.shape().isRequired,
};
OrganizationAdd.contextTypes = {
custom_virtualenvs: PropTypes.arrayOf(PropTypes.string)
};

View File

@@ -16,6 +16,7 @@ import PaginatedDataList, {
import DataListToolbar from '../../../components/DataListToolbar';
import OrganizationListItem from '../components/OrganizationListItem';
import { getQSConfig, parseNamespacedQueryString } from '../../../util/qs';
import { OrganizationsAPI } from '../../../api';
const QS_CONFIG = getQSConfig('organization', {
page: 1,
@@ -73,11 +74,11 @@ class OrganizationsList extends Component {
async handleOrgDelete () {
const { selected } = this.state;
const { api, handleHttpError } = this.props;
const { handleHttpError } = this.props;
let errorHandled;
try {
await Promise.all(selected.map((org) => api.destroyOrganization(org.id)));
await Promise.all(selected.map((org) => OrganizationsAPI.destroy(org.id)));
this.setState({
selected: []
});
@@ -91,13 +92,13 @@ class OrganizationsList extends Component {
}
async fetchOrganizations () {
const { api, handleHttpError, location } = this.props;
const { handleHttpError, location } = this.props;
const params = parseNamespacedQueryString(QS_CONFIG, location.search);
this.setState({ error: false, isLoading: true });
try {
const { data } = await api.getOrganizations(params);
const { data } = await OrganizationsAPI.read(params);
const { count, results } = data;
const stateToUpdate = {
@@ -115,10 +116,8 @@ class OrganizationsList extends Component {
}
async fetchOptionsOrganizations () {
const { api } = this.props;
try {
const { data } = await api.optionsOrganizations();
const { data } = await OrganizationsAPI.readOptions();
const { actions } = data;
const stateToUpdate = {