mirror of
https://github.com/ansible/awx.git
synced 2026-04-14 06:29:25 -02:30
updates based on pr feedback
run prettier update hasContentError to contentError in all the places function naming updates
This commit is contained in:
@@ -1,48 +1,46 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import {
|
import { encodeQueryString } from '@util/qs';
|
||||||
encodeQueryString
|
|
||||||
} from '@util/qs';
|
|
||||||
|
|
||||||
const defaultHttp = axios.create({
|
const defaultHttp = axios.create({
|
||||||
xsrfCookieName: 'csrftoken',
|
xsrfCookieName: 'csrftoken',
|
||||||
xsrfHeaderName: 'X-CSRFToken',
|
xsrfHeaderName: 'X-CSRFToken',
|
||||||
paramsSerializer(params) {
|
paramsSerializer(params) {
|
||||||
return encodeQueryString(params);
|
return encodeQueryString(params);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class Base {
|
class Base {
|
||||||
constructor (http = defaultHttp, baseURL) {
|
constructor(http = defaultHttp, baseURL) {
|
||||||
this.http = http;
|
this.http = http;
|
||||||
this.baseUrl = baseURL;
|
this.baseUrl = baseURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
create (data) {
|
create(data) {
|
||||||
return this.http.post(this.baseUrl, data);
|
return this.http.post(this.baseUrl, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy (id) {
|
destroy(id) {
|
||||||
return this.http.delete(`${this.baseUrl}${id}/`);
|
return this.http.delete(`${this.baseUrl}${id}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
read (params) {
|
read(params) {
|
||||||
return this.http.get(this.baseUrl, { params });
|
return this.http.get(this.baseUrl, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
readDetail (id) {
|
readDetail(id) {
|
||||||
return this.http.get(`${this.baseUrl}${id}/`);
|
return this.http.get(`${this.baseUrl}${id}/`);
|
||||||
}
|
}
|
||||||
|
|
||||||
readOptions () {
|
readOptions() {
|
||||||
return this.http.options(this.baseUrl);
|
return this.http.options(this.baseUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
replace (id, data) {
|
replace(id, data) {
|
||||||
return this.http.put(`${this.baseUrl}${id}/`, data);
|
return this.http.put(`${this.baseUrl}${id}/`, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
update (id, data) {
|
update(id, data) {
|
||||||
return this.http.patch(`${this.baseUrl}${id}/`, data);
|
return this.http.patch(`${this.baseUrl}${id}/`, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ import Base from './Base';
|
|||||||
describe('Base', () => {
|
describe('Base', () => {
|
||||||
const createPromise = () => Promise.resolve();
|
const createPromise = () => Promise.resolve();
|
||||||
const mockBaseURL = '/api/v2/organizations/';
|
const mockBaseURL = '/api/v2/organizations/';
|
||||||
const mockHttp = ({
|
const mockHttp = {
|
||||||
delete: jest.fn(createPromise),
|
delete: jest.fn(createPromise),
|
||||||
get: jest.fn(createPromise),
|
get: jest.fn(createPromise),
|
||||||
options: jest.fn(createPromise),
|
options: jest.fn(createPromise),
|
||||||
patch: jest.fn(createPromise),
|
patch: jest.fn(createPromise),
|
||||||
post: jest.fn(createPromise),
|
post: jest.fn(createPromise),
|
||||||
put: jest.fn(createPromise)
|
put: jest.fn(createPromise),
|
||||||
});
|
};
|
||||||
|
|
||||||
const BaseAPI = new Base(mockHttp, mockBaseURL);
|
const BaseAPI = new Base(mockHttp, mockBaseURL);
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ describe('Base', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('create calls http method with expected data', async (done) => {
|
test('create calls http method with expected data', async done => {
|
||||||
const data = { name: 'test ' };
|
const data = { name: 'test ' };
|
||||||
await BaseAPI.create(data);
|
await BaseAPI.create(data);
|
||||||
|
|
||||||
@@ -28,19 +28,21 @@ describe('Base', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('destroy calls http method with expected data', async (done) => {
|
test('destroy calls http method with expected data', async done => {
|
||||||
const resourceId = 1;
|
const resourceId = 1;
|
||||||
await BaseAPI.destroy(resourceId);
|
await BaseAPI.destroy(resourceId);
|
||||||
|
|
||||||
expect(mockHttp.delete).toHaveBeenCalledTimes(1);
|
expect(mockHttp.delete).toHaveBeenCalledTimes(1);
|
||||||
expect(mockHttp.delete.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
|
expect(mockHttp.delete.mock.calls[0][0]).toEqual(
|
||||||
|
`${mockBaseURL}${resourceId}/`
|
||||||
|
);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('read calls http method with expected data', async (done) => {
|
test('read calls http method with expected data', async done => {
|
||||||
const testParams = { foo: 'bar' };
|
const testParams = { foo: 'bar' };
|
||||||
const testParamsDuplicates = { foo: ['bar', 'baz']};
|
const testParamsDuplicates = { foo: ['bar', 'baz'] };
|
||||||
|
|
||||||
await BaseAPI.read(testParams);
|
await BaseAPI.read(testParams);
|
||||||
await BaseAPI.read();
|
await BaseAPI.read();
|
||||||
@@ -48,25 +50,29 @@ describe('Base', () => {
|
|||||||
|
|
||||||
expect(mockHttp.get).toHaveBeenCalledTimes(3);
|
expect(mockHttp.get).toHaveBeenCalledTimes(3);
|
||||||
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[0][1]).toEqual({"params": {"foo": "bar"}});
|
expect(mockHttp.get.mock.calls[0][1]).toEqual({ params: { foo: 'bar' } });
|
||||||
expect(mockHttp.get.mock.calls[1][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[1][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[1][1]).toEqual({"params": undefined});
|
expect(mockHttp.get.mock.calls[1][1]).toEqual({ params: undefined });
|
||||||
expect(mockHttp.get.mock.calls[2][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[2][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[2][1]).toEqual({"params": {"foo": ["bar", "baz"]}});
|
expect(mockHttp.get.mock.calls[2][1]).toEqual({
|
||||||
|
params: { foo: ['bar', 'baz'] },
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('readDetail calls http method with expected data', async (done) => {
|
test('readDetail calls http method with expected data', async done => {
|
||||||
const resourceId = 1;
|
const resourceId = 1;
|
||||||
|
|
||||||
await BaseAPI.readDetail(resourceId);
|
await BaseAPI.readDetail(resourceId);
|
||||||
|
|
||||||
expect(mockHttp.get).toHaveBeenCalledTimes(1);
|
expect(mockHttp.get).toHaveBeenCalledTimes(1);
|
||||||
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
|
expect(mockHttp.get.mock.calls[0][0]).toEqual(
|
||||||
|
`${mockBaseURL}${resourceId}/`
|
||||||
|
);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('readOptions calls http method with expected data', async (done) => {
|
test('readOptions calls http method with expected data', async done => {
|
||||||
await BaseAPI.readOptions();
|
await BaseAPI.readOptions();
|
||||||
|
|
||||||
expect(mockHttp.options).toHaveBeenCalledTimes(1);
|
expect(mockHttp.options).toHaveBeenCalledTimes(1);
|
||||||
@@ -74,27 +80,31 @@ describe('Base', () => {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('replace calls http method with expected data', async (done) => {
|
test('replace calls http method with expected data', async done => {
|
||||||
const resourceId = 1;
|
const resourceId = 1;
|
||||||
const data = { name: 'test ' };
|
const data = { name: 'test ' };
|
||||||
|
|
||||||
await BaseAPI.replace(resourceId, data);
|
await BaseAPI.replace(resourceId, data);
|
||||||
|
|
||||||
expect(mockHttp.put).toHaveBeenCalledTimes(1);
|
expect(mockHttp.put).toHaveBeenCalledTimes(1);
|
||||||
expect(mockHttp.put.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
|
expect(mockHttp.put.mock.calls[0][0]).toEqual(
|
||||||
|
`${mockBaseURL}${resourceId}/`
|
||||||
|
);
|
||||||
expect(mockHttp.put.mock.calls[0][1]).toEqual(data);
|
expect(mockHttp.put.mock.calls[0][1]).toEqual(data);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('update calls http method with expected data', async (done) => {
|
test('update calls http method with expected data', async done => {
|
||||||
const resourceId = 1;
|
const resourceId = 1;
|
||||||
const data = { name: 'test ' };
|
const data = { name: 'test ' };
|
||||||
|
|
||||||
await BaseAPI.update(resourceId, data);
|
await BaseAPI.update(resourceId, data);
|
||||||
|
|
||||||
expect(mockHttp.patch).toHaveBeenCalledTimes(1);
|
expect(mockHttp.patch).toHaveBeenCalledTimes(1);
|
||||||
expect(mockHttp.patch.mock.calls[0][0]).toEqual(`${mockBaseURL}${resourceId}/`);
|
expect(mockHttp.patch.mock.calls[0][0]).toEqual(
|
||||||
|
`${mockBaseURL}${resourceId}/`
|
||||||
|
);
|
||||||
expect(mockHttp.patch.mock.calls[0][1]).toEqual(data);
|
expect(mockHttp.patch.mock.calls[0][1]).toEqual(data);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
const InstanceGroupsMixin = (parent) => class extends parent {
|
const InstanceGroupsMixin = parent =>
|
||||||
readInstanceGroups (resourceId, params) {
|
class extends parent {
|
||||||
return this.http.get(`${this.baseUrl}${resourceId}/instance_groups/`, params);
|
readInstanceGroups(resourceId, params) {
|
||||||
}
|
return this.http.get(
|
||||||
|
`${this.baseUrl}${resourceId}/instance_groups/`,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
associateInstanceGroup (resourceId, instanceGroupId) {
|
associateInstanceGroup(resourceId, instanceGroupId) {
|
||||||
return this.http.post(`${this.baseUrl}${resourceId}/instance_groups/`, { id: instanceGroupId });
|
return this.http.post(`${this.baseUrl}${resourceId}/instance_groups/`, {
|
||||||
}
|
id: instanceGroupId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
disassociateInstanceGroup (resourceId, instanceGroupId) {
|
disassociateInstanceGroup(resourceId, instanceGroupId) {
|
||||||
return this.http.post(`${this.baseUrl}${resourceId}/instance_groups/`, { id: instanceGroupId, disassociate: true });
|
return this.http.post(`${this.baseUrl}${resourceId}/instance_groups/`, {
|
||||||
}
|
id: instanceGroupId,
|
||||||
};
|
disassociate: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default InstanceGroupsMixin;
|
export default InstanceGroupsMixin;
|
||||||
|
|||||||
@@ -1,65 +1,106 @@
|
|||||||
const NotificationsMixin = (parent) => class extends parent {
|
const NotificationsMixin = parent =>
|
||||||
readOptionsNotificationTemplates(id) {
|
class extends parent {
|
||||||
return this.http.options(`${this.baseUrl}${id}/notification_templates/`);
|
readOptionsNotificationTemplates(id) {
|
||||||
}
|
return this.http.options(`${this.baseUrl}${id}/notification_templates/`);
|
||||||
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a helper method meant to simplify setting the "on" or "off" status of
|
|
||||||
* a related notification.
|
|
||||||
*
|
|
||||||
* @param[resourceId] - id of the base resource
|
|
||||||
* @param[notificationId] - id of the notification
|
|
||||||
* @param[notificationType] - the type of notification, options are "success" and "error"
|
|
||||||
* @param[associationState] - Boolean for associating or disassociating, options are true or false
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
updateNotificationTemplateAssociation (resourceId, notificationId, notificationType, associationState) {
|
|
||||||
if (notificationType === 'success' && associationState === true) {
|
|
||||||
return this.associateNotificationTemplatesSuccess(resourceId, notificationId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationType === 'success' && associationState === false) {
|
readNotificationTemplates(id, params) {
|
||||||
return this.disassociateNotificationTemplatesSuccess(resourceId, notificationId);
|
return this.http.get(
|
||||||
|
`${this.baseUrl}${id}/notification_templates/`,
|
||||||
|
params
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationType === 'error' && associationState === true) {
|
readNotificationTemplatesSuccess(id, params) {
|
||||||
return this.associateNotificationTemplatesError(resourceId, notificationId);
|
return this.http.get(
|
||||||
|
`${this.baseUrl}${id}/notification_templates_success/`,
|
||||||
|
params
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notificationType === 'error' && associationState === false) {
|
readNotificationTemplatesError(id, params) {
|
||||||
return this.disassociateNotificationTemplatesError(resourceId, notificationId);
|
return this.http.get(
|
||||||
|
`${this.baseUrl}${id}/notification_templates_error/`,
|
||||||
|
params
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unsupported notificationType, associationState combination: ${notificationType}, ${associationState}`);
|
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 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a helper method meant to simplify setting the "on" or "off" status of
|
||||||
|
* a related notification.
|
||||||
|
*
|
||||||
|
* @param[resourceId] - id of the base resource
|
||||||
|
* @param[notificationId] - id of the notification
|
||||||
|
* @param[notificationType] - the type of notification, options are "success" and "error"
|
||||||
|
* @param[associationState] - Boolean for associating or disassociating, options are true or false
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
updateNotificationTemplateAssociation(
|
||||||
|
resourceId,
|
||||||
|
notificationId,
|
||||||
|
notificationType,
|
||||||
|
associationState
|
||||||
|
) {
|
||||||
|
if (notificationType === 'success' && associationState === true) {
|
||||||
|
return this.associateNotificationTemplatesSuccess(
|
||||||
|
resourceId,
|
||||||
|
notificationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationType === 'success' && associationState === false) {
|
||||||
|
return this.disassociateNotificationTemplatesSuccess(
|
||||||
|
resourceId,
|
||||||
|
notificationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationType === 'error' && associationState === true) {
|
||||||
|
return this.associateNotificationTemplatesError(
|
||||||
|
resourceId,
|
||||||
|
notificationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notificationType === 'error' && associationState === false) {
|
||||||
|
return this.disassociateNotificationTemplatesError(
|
||||||
|
resourceId,
|
||||||
|
notificationId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Unsupported notificationType, associationState combination: ${notificationType}, ${associationState}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default NotificationsMixin;
|
export default NotificationsMixin;
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ import NotificationsMixin from '../mixins/Notifications.mixin';
|
|||||||
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
|
import InstanceGroupsMixin from '../mixins/InstanceGroups.mixin';
|
||||||
|
|
||||||
class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) {
|
class Organizations extends InstanceGroupsMixin(NotificationsMixin(Base)) {
|
||||||
constructor (http) {
|
constructor(http) {
|
||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = '/api/v2/organizations/';
|
this.baseUrl = '/api/v2/organizations/';
|
||||||
}
|
}
|
||||||
|
|
||||||
readAccessList (id, params) {
|
readAccessList(id, params) {
|
||||||
return this.http.get(`${this.baseUrl}${id}/access_list/`, { params });
|
return this.http.get(`${this.baseUrl}${id}/access_list/`, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
readTeams (id, params) {
|
readTeams(id, params) {
|
||||||
return this.http.get(`${this.baseUrl}${id}/teams/`, { params });
|
return this.http.get(`${this.baseUrl}${id}/teams/`, { params });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { describeNotificationMixin } from '../../../testUtils/apiReusable';
|
|||||||
describe('OrganizationsAPI', () => {
|
describe('OrganizationsAPI', () => {
|
||||||
const orgId = 1;
|
const orgId = 1;
|
||||||
const createPromise = () => Promise.resolve();
|
const createPromise = () => Promise.resolve();
|
||||||
const mockHttp = ({ get: jest.fn(createPromise) });
|
const mockHttp = { get: jest.fn(createPromise) };
|
||||||
|
|
||||||
const OrganizationsAPI = new Organizations(mockHttp);
|
const OrganizationsAPI = new Organizations(mockHttp);
|
||||||
|
|
||||||
@@ -12,9 +12,9 @@ describe('OrganizationsAPI', () => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('read access list calls get with expected params', async (done) => {
|
test('read access list calls get with expected params', async done => {
|
||||||
const testParams = { foo: 'bar' };
|
const testParams = { foo: 'bar' };
|
||||||
const testParamsDuplicates = { foo: ['bar', 'baz']};
|
const testParamsDuplicates = { foo: ['bar', 'baz'] };
|
||||||
|
|
||||||
const mockBaseURL = `/api/v2/organizations/${orgId}/access_list/`;
|
const mockBaseURL = `/api/v2/organizations/${orgId}/access_list/`;
|
||||||
|
|
||||||
@@ -24,17 +24,19 @@ describe('OrganizationsAPI', () => {
|
|||||||
|
|
||||||
expect(mockHttp.get).toHaveBeenCalledTimes(3);
|
expect(mockHttp.get).toHaveBeenCalledTimes(3);
|
||||||
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[0][1]).toEqual({"params": undefined});
|
expect(mockHttp.get.mock.calls[0][1]).toEqual({ params: undefined });
|
||||||
expect(mockHttp.get.mock.calls[1][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[1][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[1][1]).toEqual({"params": {"foo": "bar"}});
|
expect(mockHttp.get.mock.calls[1][1]).toEqual({ params: { foo: 'bar' } });
|
||||||
expect(mockHttp.get.mock.calls[2][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[2][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[2][1]).toEqual({"params": {"foo": ["bar", "baz"]}});
|
expect(mockHttp.get.mock.calls[2][1]).toEqual({
|
||||||
|
params: { foo: ['bar', 'baz'] },
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('read teams calls get with expected params', async (done) => {
|
test('read teams calls get with expected params', async done => {
|
||||||
const testParams = { foo: 'bar' };
|
const testParams = { foo: 'bar' };
|
||||||
const testParamsDuplicates = { foo: ['bar', 'baz']};
|
const testParamsDuplicates = { foo: ['bar', 'baz'] };
|
||||||
|
|
||||||
const mockBaseURL = `/api/v2/organizations/${orgId}/teams/`;
|
const mockBaseURL = `/api/v2/organizations/${orgId}/teams/`;
|
||||||
|
|
||||||
@@ -44,11 +46,13 @@ describe('OrganizationsAPI', () => {
|
|||||||
|
|
||||||
expect(mockHttp.get).toHaveBeenCalledTimes(3);
|
expect(mockHttp.get).toHaveBeenCalledTimes(3);
|
||||||
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[0][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[0][1]).toEqual({"params": undefined});
|
expect(mockHttp.get.mock.calls[0][1]).toEqual({ params: undefined });
|
||||||
expect(mockHttp.get.mock.calls[1][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[1][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[1][1]).toEqual({"params": {"foo": "bar"}});
|
expect(mockHttp.get.mock.calls[1][1]).toEqual({ params: { foo: 'bar' } });
|
||||||
expect(mockHttp.get.mock.calls[2][0]).toEqual(`${mockBaseURL}`);
|
expect(mockHttp.get.mock.calls[2][0]).toEqual(`${mockBaseURL}`);
|
||||||
expect(mockHttp.get.mock.calls[2][1]).toEqual({"params": {"foo": ["bar", "baz"]}});
|
expect(mockHttp.get.mock.calls[2][1]).toEqual({
|
||||||
|
params: { foo: ['bar', 'baz'] },
|
||||||
|
});
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,14 +8,13 @@ import SelectRoleStep from './SelectRoleStep';
|
|||||||
import SelectableCard from './SelectableCard';
|
import SelectableCard from './SelectableCard';
|
||||||
import { TeamsAPI, UsersAPI } from '../../api';
|
import { TeamsAPI, UsersAPI } from '../../api';
|
||||||
|
|
||||||
const readUsers = async (queryParams) => UsersAPI.read(
|
const readUsers = async queryParams =>
|
||||||
Object.assign(queryParams, { is_superuser: false })
|
UsersAPI.read(Object.assign(queryParams, { is_superuser: false }));
|
||||||
);
|
|
||||||
|
|
||||||
const readTeams = async (queryParams) => TeamsAPI.read(queryParams);
|
const readTeams = async queryParams => TeamsAPI.read(queryParams);
|
||||||
|
|
||||||
class AddResourceRole extends React.Component {
|
class AddResourceRole extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -25,67 +24,69 @@ class AddResourceRole extends React.Component {
|
|||||||
currentStepId: 1,
|
currentStepId: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.handleResourceCheckboxClick = this.handleResourceCheckboxClick.bind(this);
|
this.handleResourceCheckboxClick = this.handleResourceCheckboxClick.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
this.handleResourceSelect = this.handleResourceSelect.bind(this);
|
this.handleResourceSelect = this.handleResourceSelect.bind(this);
|
||||||
this.handleRoleCheckboxClick = this.handleRoleCheckboxClick.bind(this);
|
this.handleRoleCheckboxClick = this.handleRoleCheckboxClick.bind(this);
|
||||||
this.handleWizardNext = this.handleWizardNext.bind(this);
|
this.handleWizardNext = this.handleWizardNext.bind(this);
|
||||||
this.handleWizardSave = this.handleWizardSave.bind(this);
|
this.handleWizardSave = this.handleWizardSave.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResourceCheckboxClick (user) {
|
handleResourceCheckboxClick(user) {
|
||||||
const { selectedResourceRows } = this.state;
|
const { selectedResourceRows } = this.state;
|
||||||
|
|
||||||
const selectedIndex = selectedResourceRows
|
const selectedIndex = selectedResourceRows.findIndex(
|
||||||
.findIndex(selectedRow => selectedRow.id === user.id);
|
selectedRow => selectedRow.id === user.id
|
||||||
|
);
|
||||||
|
|
||||||
if (selectedIndex > -1) {
|
if (selectedIndex > -1) {
|
||||||
selectedResourceRows.splice(selectedIndex, 1);
|
selectedResourceRows.splice(selectedIndex, 1);
|
||||||
this.setState({ selectedResourceRows });
|
this.setState({ selectedResourceRows });
|
||||||
} else {
|
} else {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
selectedResourceRows: [...prevState.selectedResourceRows, user]
|
selectedResourceRows: [...prevState.selectedResourceRows, user],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRoleCheckboxClick (role) {
|
handleRoleCheckboxClick(role) {
|
||||||
const { selectedRoleRows } = this.state;
|
const { selectedRoleRows } = this.state;
|
||||||
|
|
||||||
const selectedIndex = selectedRoleRows
|
const selectedIndex = selectedRoleRows.findIndex(
|
||||||
.findIndex(selectedRow => selectedRow.id === role.id);
|
selectedRow => selectedRow.id === role.id
|
||||||
|
);
|
||||||
|
|
||||||
if (selectedIndex > -1) {
|
if (selectedIndex > -1) {
|
||||||
selectedRoleRows.splice(selectedIndex, 1);
|
selectedRoleRows.splice(selectedIndex, 1);
|
||||||
this.setState({ selectedRoleRows });
|
this.setState({ selectedRoleRows });
|
||||||
} else {
|
} else {
|
||||||
this.setState(prevState => ({
|
this.setState(prevState => ({
|
||||||
selectedRoleRows: [...prevState.selectedRoleRows, role]
|
selectedRoleRows: [...prevState.selectedRoleRows, role],
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleResourceSelect (resourceType) {
|
handleResourceSelect(resourceType) {
|
||||||
this.setState({
|
this.setState({
|
||||||
selectedResource: resourceType,
|
selectedResource: resourceType,
|
||||||
selectedResourceRows: [],
|
selectedResourceRows: [],
|
||||||
selectedRoleRows: []
|
selectedRoleRows: [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWizardNext (step) {
|
handleWizardNext(step) {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentStepId: step.id,
|
currentStepId: step.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleWizardSave () {
|
async handleWizardSave() {
|
||||||
const {
|
const { onSave } = this.props;
|
||||||
onSave
|
|
||||||
} = this.props;
|
|
||||||
const {
|
const {
|
||||||
selectedResourceRows,
|
selectedResourceRows,
|
||||||
selectedRoleRows,
|
selectedRoleRows,
|
||||||
selectedResource
|
selectedResource,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -95,11 +96,17 @@ class AddResourceRole extends React.Component {
|
|||||||
for (let j = 0; j < selectedRoleRows.length; j++) {
|
for (let j = 0; j < selectedRoleRows.length; j++) {
|
||||||
if (selectedResource === 'users') {
|
if (selectedResource === 'users') {
|
||||||
roleRequests.push(
|
roleRequests.push(
|
||||||
UsersAPI.associateRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
|
UsersAPI.associateRole(
|
||||||
|
selectedResourceRows[i].id,
|
||||||
|
selectedRoleRows[j].id
|
||||||
|
)
|
||||||
);
|
);
|
||||||
} else if (selectedResource === 'teams') {
|
} else if (selectedResource === 'teams') {
|
||||||
roleRequests.push(
|
roleRequests.push(
|
||||||
TeamsAPI.associateRole(selectedResourceRows[i].id, selectedRoleRows[j].id)
|
TeamsAPI.associateRole(
|
||||||
|
selectedResourceRows[i].id,
|
||||||
|
selectedRoleRows[j].id
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,25 +119,31 @@ class AddResourceRole extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
selectedResource,
|
selectedResource,
|
||||||
selectedResourceRows,
|
selectedResourceRows,
|
||||||
selectedRoleRows,
|
selectedRoleRows,
|
||||||
currentStepId,
|
currentStepId,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const { onClose, roles, i18n } = this.props;
|
||||||
onClose,
|
|
||||||
roles,
|
|
||||||
i18n
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const userColumns = [
|
const userColumns = [
|
||||||
{ name: i18n._(t`Username`), key: 'username', isSortable: true, isSearchable: true }
|
{
|
||||||
|
name: i18n._(t`Username`),
|
||||||
|
key: 'username',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const teamColumns = [
|
const teamColumns = [
|
||||||
{ name: i18n._(t`Name`), key: 'name', isSortable: true, isSearchable: true }
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
let wizardTitle = '';
|
let wizardTitle = '';
|
||||||
@@ -164,7 +177,7 @@ class AddResourceRole extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
enableNext: selectedResource !== null
|
enableNext: selectedResource !== null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@@ -195,7 +208,7 @@ class AddResourceRole extends React.Component {
|
|||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
),
|
),
|
||||||
enableNext: selectedResourceRows.length > 0
|
enableNext: selectedResourceRows.length > 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
@@ -211,8 +224,8 @@ class AddResourceRole extends React.Component {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
nextButtonText: i18n._(t`Save`),
|
nextButtonText: i18n._(t`Save`),
|
||||||
enableNext: selectedRoleRows.length > 0
|
enableNext: selectedRoleRows.length > 0,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const currentStep = steps.find(step => step.id === currentStepId);
|
const currentStep = steps.find(step => step.id === currentStepId);
|
||||||
@@ -236,11 +249,11 @@ class AddResourceRole extends React.Component {
|
|||||||
AddResourceRole.propTypes = {
|
AddResourceRole.propTypes = {
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onSave: PropTypes.func.isRequired,
|
onSave: PropTypes.func.isRequired,
|
||||||
roles: PropTypes.shape()
|
roles: PropTypes.shape(),
|
||||||
};
|
};
|
||||||
|
|
||||||
AddResourceRole.defaultProps = {
|
AddResourceRole.defaultProps = {
|
||||||
roles: {}
|
roles: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export { AddResourceRole as _AddResourceRole };
|
export { AddResourceRole as _AddResourceRole };
|
||||||
|
|||||||
@@ -40,10 +40,7 @@ class SelectResourceStep extends React.Component {
|
|||||||
|
|
||||||
async readResourceList() {
|
async readResourceList() {
|
||||||
const { onSearch, location } = this.props;
|
const { onSearch, location } = this.props;
|
||||||
const queryParams = parseQueryString(
|
const queryParams = parseQueryString(this.qsConfig, location.search);
|
||||||
this.qsConfig,
|
|
||||||
location.search
|
|
||||||
);
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import SelectResourceStep from './SelectResourceStep';
|
|||||||
|
|
||||||
describe('<SelectResourceStep />', () => {
|
describe('<SelectResourceStep />', () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'Username', key: 'username', isSortable: true, isSearchable: true }
|
{ name: 'Username', key: 'username', isSortable: true, isSearchable: true },
|
||||||
];
|
];
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.restoreAllMocks();
|
jest.restoreAllMocks();
|
||||||
@@ -30,9 +30,9 @@ describe('<SelectResourceStep />', () => {
|
|||||||
count: 2,
|
count: 2,
|
||||||
results: [
|
results: [
|
||||||
{ id: 1, username: 'foo', url: 'item/1' },
|
{ id: 1, username: 'foo', url: 'item/1' },
|
||||||
{ id: 2, username: 'bar', url: 'item/2' }
|
{ id: 2, username: 'bar', url: 'item/2' },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
mountWithContexts(
|
mountWithContexts(
|
||||||
<SelectResourceStep
|
<SelectResourceStep
|
||||||
@@ -46,25 +46,25 @@ describe('<SelectResourceStep />', () => {
|
|||||||
expect(handleSearch).toHaveBeenCalledWith({
|
expect(handleSearch).toHaveBeenCalledWith({
|
||||||
order_by: 'username',
|
order_by: 'username',
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 5
|
page_size: 5,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('readResourceList properly adds rows to state', async () => {
|
test('readResourceList properly adds rows to state', async () => {
|
||||||
const selectedResourceRows = [
|
const selectedResourceRows = [{ id: 1, username: 'foo', url: 'item/1' }];
|
||||||
{ id: 1, username: 'foo', url: 'item/1' }
|
|
||||||
];
|
|
||||||
const handleSearch = jest.fn().mockResolvedValue({
|
const handleSearch = jest.fn().mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
count: 2,
|
count: 2,
|
||||||
results: [
|
results: [
|
||||||
{ id: 1, username: 'foo', url: 'item/1' },
|
{ id: 1, username: 'foo', url: 'item/1' },
|
||||||
{ id: 2, username: 'bar', url: 'item/2' }
|
{ id: 2, username: 'bar', url: 'item/2' },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/organizations/1/access?resource.page=1&resource.order_by=-username'],
|
initialEntries: [
|
||||||
|
'/organizations/1/access?resource.page=1&resource.order_by=-username',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const wrapper = await mountWithContexts(
|
const wrapper = await mountWithContexts(
|
||||||
<SelectResourceStep
|
<SelectResourceStep
|
||||||
@@ -74,7 +74,10 @@ describe('<SelectResourceStep />', () => {
|
|||||||
onSearch={handleSearch}
|
onSearch={handleSearch}
|
||||||
selectedResourceRows={selectedResourceRows}
|
selectedResourceRows={selectedResourceRows}
|
||||||
sortedColumnKey="username"
|
sortedColumnKey="username"
|
||||||
/>, { context: { router: { history, route: { location: history.location } } } }
|
/>,
|
||||||
|
{
|
||||||
|
context: { router: { history, route: { location: history.location } } },
|
||||||
|
}
|
||||||
).find('SelectResourceStep');
|
).find('SelectResourceStep');
|
||||||
await wrapper.instance().readResourceList();
|
await wrapper.instance().readResourceList();
|
||||||
expect(handleSearch).toHaveBeenCalledWith({
|
expect(handleSearch).toHaveBeenCalledWith({
|
||||||
@@ -84,7 +87,7 @@ describe('<SelectResourceStep />', () => {
|
|||||||
});
|
});
|
||||||
expect(wrapper.state('resources')).toEqual([
|
expect(wrapper.state('resources')).toEqual([
|
||||||
{ id: 1, username: 'foo', url: 'item/1' },
|
{ id: 1, username: 'foo', url: 'item/1' },
|
||||||
{ id: 2, username: 'bar', url: 'item/2' }
|
{ id: 2, username: 'bar', url: 'item/2' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,8 +97,8 @@ describe('<SelectResourceStep />', () => {
|
|||||||
count: 2,
|
count: 2,
|
||||||
results: [
|
results: [
|
||||||
{ id: 1, username: 'foo', url: 'item/1' },
|
{ id: 1, username: 'foo', url: 'item/1' },
|
||||||
{ id: 2, username: 'bar', url: 'item/2' }
|
{ id: 2, username: 'bar', url: 'item/2' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<SelectResourceStep
|
<SelectResourceStep
|
||||||
@@ -111,7 +114,9 @@ describe('<SelectResourceStep />', () => {
|
|||||||
wrapper.update();
|
wrapper.update();
|
||||||
const checkboxListItemWrapper = wrapper.find('CheckboxListItem');
|
const checkboxListItemWrapper = wrapper.find('CheckboxListItem');
|
||||||
expect(checkboxListItemWrapper.length).toBe(2);
|
expect(checkboxListItemWrapper.length).toBe(2);
|
||||||
checkboxListItemWrapper.first().find('input[type="checkbox"]')
|
checkboxListItemWrapper
|
||||||
|
.first()
|
||||||
|
.find('input[type="checkbox"]')
|
||||||
.simulate('change', { target: { checked: true } });
|
.simulate('change', { target: { checked: true } });
|
||||||
expect(handleRowClick).toHaveBeenCalledWith(data.results[0]);
|
expect(handleRowClick).toHaveBeenCalledWith(data.results[0]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,14 +3,21 @@ import { number, bool } from 'prop-types';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import Chip from './Chip';
|
import Chip from './Chip';
|
||||||
|
|
||||||
const ChipGroup = ({ children, className, showOverflowAfter, displayAll, ...props }) => {
|
const ChipGroup = ({
|
||||||
|
children,
|
||||||
|
className,
|
||||||
|
showOverflowAfter,
|
||||||
|
displayAll,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
const [isExpanded, setIsExpanded] = useState(!showOverflowAfter);
|
const [isExpanded, setIsExpanded] = useState(!showOverflowAfter);
|
||||||
const toggleIsOpen = () => setIsExpanded(!isExpanded);
|
const toggleIsOpen = () => setIsExpanded(!isExpanded);
|
||||||
|
|
||||||
const mappedChildren = React.Children.map(children, c => (
|
const mappedChildren = React.Children.map(children, c =>
|
||||||
React.cloneElement(c, { component: 'li' })
|
React.cloneElement(c, { component: 'li' })
|
||||||
));
|
);
|
||||||
const showOverflowToggle = showOverflowAfter && children.length > showOverflowAfter;
|
const showOverflowToggle =
|
||||||
|
showOverflowAfter && children.length > showOverflowAfter;
|
||||||
const numToShow = isExpanded
|
const numToShow = isExpanded
|
||||||
? children.length
|
? children.length
|
||||||
: Math.min(showOverflowAfter, children.length);
|
: Math.min(showOverflowAfter, children.length);
|
||||||
@@ -30,17 +37,17 @@ const ChipGroup = ({ children, className, showOverflowAfter, displayAll, ...prop
|
|||||||
};
|
};
|
||||||
ChipGroup.propTypes = {
|
ChipGroup.propTypes = {
|
||||||
showOverflowAfter: number,
|
showOverflowAfter: number,
|
||||||
displayAll: bool
|
displayAll: bool,
|
||||||
};
|
};
|
||||||
ChipGroup.defaultProps = {
|
ChipGroup.defaultProps = {
|
||||||
showOverflowAfter: null,
|
showOverflowAfter: null,
|
||||||
displayAll: false
|
displayAll: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default styled(ChipGroup)`
|
export default styled(ChipGroup)`
|
||||||
--pf-c-chip-group--c-chip--MarginRight: 10px;
|
--pf-c-chip-group--c-chip--MarginRight: 10px;
|
||||||
--pf-c-chip-group--c-chip--MarginBottom: 10px;
|
--pf-c-chip-group--c-chip--MarginBottom: 10px;
|
||||||
|
|
||||||
> .pf-c-chip.pf-m-overflow button {
|
> .pf-c-chip.pf-m-overflow button {
|
||||||
padding: 3px 8px;
|
padding: 3px 8px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ describe('<DataListToolbar />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it triggers the expected callbacks', () => {
|
test('it triggers the expected callbacks', () => {
|
||||||
const columns = [{ name: 'Name', key: 'name', isSortable: true, isSearchable: true }];
|
const columns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||||
|
];
|
||||||
|
|
||||||
const search = 'button[aria-label="Search submit button"]';
|
const search = 'button[aria-label="Search submit button"]';
|
||||||
const searchTextInput = 'input[aria-label="Search text input"]';
|
const searchTextInput = 'input[aria-label="Search text input"]';
|
||||||
@@ -59,14 +61,16 @@ describe('<DataListToolbar />', () => {
|
|||||||
test('dropdown items sortable/searchable columns work', () => {
|
test('dropdown items sortable/searchable columns work', () => {
|
||||||
const sortDropdownToggleSelector = 'button[id="awx-sort"]';
|
const sortDropdownToggleSelector = 'button[id="awx-sort"]';
|
||||||
const searchDropdownToggleSelector = 'button[id="awx-search"]';
|
const searchDropdownToggleSelector = 'button[id="awx-search"]';
|
||||||
const sortDropdownMenuItems = 'DropdownMenu > ul[aria-labelledby="awx-sort"]';
|
const sortDropdownMenuItems =
|
||||||
const searchDropdownMenuItems = 'DropdownMenu > ul[aria-labelledby="awx-search"]';
|
'DropdownMenu > ul[aria-labelledby="awx-sort"]';
|
||||||
|
const searchDropdownMenuItems =
|
||||||
|
'DropdownMenu > ul[aria-labelledby="awx-search"]';
|
||||||
|
|
||||||
const multipleColumns = [
|
const multipleColumns = [
|
||||||
{ name: 'Foo', key: 'foo', isSortable: true, isSearchable: true },
|
{ name: 'Foo', key: 'foo', isSortable: true, isSearchable: true },
|
||||||
{ name: 'Bar', key: 'bar', isSortable: true, isSearchable: true },
|
{ name: 'Bar', key: 'bar', isSortable: true, isSearchable: true },
|
||||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
{ name: 'Baz', key: 'baz' }
|
{ name: 'Baz', key: 'baz' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
@@ -103,12 +107,16 @@ describe('<DataListToolbar />', () => {
|
|||||||
);
|
);
|
||||||
toolbar.update();
|
toolbar.update();
|
||||||
|
|
||||||
const sortDropdownToggleDescending = toolbar.find(sortDropdownToggleSelector);
|
const sortDropdownToggleDescending = toolbar.find(
|
||||||
|
sortDropdownToggleSelector
|
||||||
|
);
|
||||||
expect(sortDropdownToggleDescending.length).toBe(1);
|
expect(sortDropdownToggleDescending.length).toBe(1);
|
||||||
sortDropdownToggleDescending.simulate('click');
|
sortDropdownToggleDescending.simulate('click');
|
||||||
toolbar.update();
|
toolbar.update();
|
||||||
|
|
||||||
const sortDropdownItemsDescending = toolbar.find(sortDropdownMenuItems).children();
|
const sortDropdownItemsDescending = toolbar
|
||||||
|
.find(sortDropdownMenuItems)
|
||||||
|
.children();
|
||||||
expect(sortDropdownItemsDescending.length).toBe(2);
|
expect(sortDropdownItemsDescending.length).toBe(2);
|
||||||
sortDropdownToggleDescending.simulate('click'); // toggle close the sort dropdown
|
sortDropdownToggleDescending.simulate('click'); // toggle close the sort dropdown
|
||||||
|
|
||||||
@@ -134,8 +142,12 @@ describe('<DataListToolbar />', () => {
|
|||||||
const downAlphaIconSelector = 'SortAlphaDownIcon';
|
const downAlphaIconSelector = 'SortAlphaDownIcon';
|
||||||
const upAlphaIconSelector = 'SortAlphaUpIcon';
|
const upAlphaIconSelector = 'SortAlphaUpIcon';
|
||||||
|
|
||||||
const numericColumns = [{ name: 'ID', key: 'id', isSortable: true, isNumeric: true }];
|
const numericColumns = [
|
||||||
const alphaColumns = [{ name: 'Name', key: 'name', isSortable: true, isNumeric: false }];
|
{ name: 'ID', key: 'id', isSortable: true, isNumeric: true },
|
||||||
|
];
|
||||||
|
const alphaColumns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true, isNumeric: false },
|
||||||
|
];
|
||||||
|
|
||||||
toolbar = mountWithContexts(
|
toolbar = mountWithContexts(
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
@@ -183,7 +195,9 @@ describe('<DataListToolbar />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should render additionalControls', () => {
|
test('should render additionalControls', () => {
|
||||||
const columns = [{ name: 'Name', key: 'name', isSortable: true, isSearchable: true }];
|
const columns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||||
|
];
|
||||||
const onSearch = jest.fn();
|
const onSearch = jest.fn();
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
const onSelectAll = jest.fn();
|
const onSelectAll = jest.fn();
|
||||||
@@ -194,7 +208,11 @@ describe('<DataListToolbar />', () => {
|
|||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
onSort={onSort}
|
onSort={onSort}
|
||||||
onSelectAll={onSelectAll}
|
onSelectAll={onSelectAll}
|
||||||
additionalControls={[<button key="1" id="test" type="button">click</button>]}
|
additionalControls={[
|
||||||
|
<button key="1" id="test" type="button">
|
||||||
|
click
|
||||||
|
</button>,
|
||||||
|
]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ import { ChipGroup, Chip } from '@components/Chip';
|
|||||||
import VerticalSeparator from '@components/VerticalSeparator';
|
import VerticalSeparator from '@components/VerticalSeparator';
|
||||||
|
|
||||||
const FilterTagsRow = styled.div`
|
const FilterTagsRow = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
border-top: 1px solid #d2d2d2;
|
border-top: 1px solid #d2d2d2;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ResultCount = styled.span`
|
const ResultCount = styled.span`
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FilterLabel = styled.span`
|
const FilterLabel = styled.span`
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// remove non-default query params so they don't show up as filter tags
|
// remove non-default query params so they don't show up as filter tags
|
||||||
@@ -30,50 +30,59 @@ const filterDefaultParams = (paramsArr, config) => {
|
|||||||
return paramsArr.filter(key => defaultParamsKeys.indexOf(key) === -1);
|
return paramsArr.filter(key => defaultParamsKeys.indexOf(key) === -1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const FilterTags = ({ i18n, itemCount, qsConfig, location, onRemove, onRemoveAll }) => {
|
const FilterTags = ({
|
||||||
|
i18n,
|
||||||
|
itemCount,
|
||||||
|
qsConfig,
|
||||||
|
location,
|
||||||
|
onRemove,
|
||||||
|
onRemoveAll,
|
||||||
|
}) => {
|
||||||
const queryParams = parseQueryString(qsConfig, location.search);
|
const queryParams = parseQueryString(qsConfig, location.search);
|
||||||
const queryParamsArr = [];
|
const queryParamsArr = [];
|
||||||
const displayAll = true;
|
const displayAll = true;
|
||||||
const nonDefaultParams = filterDefaultParams(Object.keys(queryParams), qsConfig);
|
const nonDefaultParams = filterDefaultParams(
|
||||||
nonDefaultParams
|
Object.keys(queryParams),
|
||||||
.forEach(key => {
|
qsConfig
|
||||||
if (Array.isArray(queryParams[key])) {
|
);
|
||||||
queryParams[key].forEach(val => queryParamsArr.push({ key, value: val }));
|
nonDefaultParams.forEach(key => {
|
||||||
} else {
|
if (Array.isArray(queryParams[key])) {
|
||||||
queryParamsArr.push({ key, value: queryParams[key] });
|
queryParams[key].forEach(val => queryParamsArr.push({ key, value: val }));
|
||||||
}
|
} else {
|
||||||
});
|
queryParamsArr.push({ key, value: queryParams[key] });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (queryParamsArr.length > 0) && (
|
return (
|
||||||
<FilterTagsRow>
|
queryParamsArr.length > 0 && (
|
||||||
<ResultCount>
|
<FilterTagsRow>
|
||||||
{`${itemCount} results`}
|
<ResultCount>{`${itemCount} results`}</ResultCount>
|
||||||
</ResultCount>
|
<VerticalSeparator />
|
||||||
<VerticalSeparator />
|
<FilterLabel>{i18n._(t`Active Filters:`)}</FilterLabel>
|
||||||
<FilterLabel>{i18n._(t`Active Filters:`)}</FilterLabel>
|
<ChipGroup displayAll={displayAll}>
|
||||||
<ChipGroup displayAll={displayAll}>
|
{queryParamsArr.map(({ key, value }) => (
|
||||||
{queryParamsArr.map(({ key, value }) => (
|
<Chip
|
||||||
<Chip
|
className="searchTagChip"
|
||||||
className="searchTagChip"
|
key={`${key}__${value}`}
|
||||||
key={`${key}__${value}`}
|
isReadOnly={false}
|
||||||
isReadOnly={false}
|
onClick={() => onRemove(key, value)}
|
||||||
onClick={() => onRemove(key, value)}
|
>
|
||||||
>
|
{value}
|
||||||
{value}
|
</Chip>
|
||||||
</Chip>
|
))}
|
||||||
))}
|
<div className="pf-c-chip pf-m-overflow">
|
||||||
<div className="pf-c-chip pf-m-overflow">
|
<Button
|
||||||
<Button
|
variant="plain"
|
||||||
variant="plain"
|
type="button"
|
||||||
type="button"
|
aria-label={i18n._(t`Clear all search filters`)}
|
||||||
aria-label={i18n._(t`Clear all search filters`)}
|
onClick={onRemoveAll}
|
||||||
onClick={onRemoveAll}
|
>
|
||||||
>
|
<span className="pf-c-chip__text">{i18n._(t`Clear all`)}</span>
|
||||||
<span className="pf-c-chip__text">{i18n._(t`Clear all`)}</span>
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</ChipGroup>
|
||||||
</ChipGroup>
|
</FilterTagsRow>
|
||||||
</FilterTagsRow>
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,14 +26,19 @@ describe('<ExpandCollapse />', () => {
|
|||||||
|
|
||||||
test('renders non-default param tags based on location history', () => {
|
test('renders non-default param tags based on location history', () => {
|
||||||
const history = createMemoryHistory({
|
const history = createMemoryHistory({
|
||||||
initialEntries: ['/foo?item.page=1&item.page_size=2&item.foo=bar&item.baz=bust'],
|
initialEntries: [
|
||||||
|
'/foo?item.page=1&item.page_size=2&item.foo=bar&item.baz=bust',
|
||||||
|
],
|
||||||
});
|
});
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<FilterTags
|
<FilterTags
|
||||||
qsConfig={qsConfig}
|
qsConfig={qsConfig}
|
||||||
onRemove={onRemoveFn}
|
onRemove={onRemoveFn}
|
||||||
onRemoveAll={onRemoveAllFn}
|
onRemoveAll={onRemoveAllFn}
|
||||||
/>, { context: { router: { history, route: { location: history.location } } } }
|
/>,
|
||||||
|
{
|
||||||
|
context: { router: { history, route: { location: history.location } } },
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const chips = wrapper.find('.pf-c-chip.searchTagChip');
|
const chips = wrapper.find('.pf-c-chip.searchTagChip');
|
||||||
expect(chips.length).toBe(2);
|
expect(chips.length).toBe(2);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
encodeNonDefaultQueryString,
|
encodeNonDefaultQueryString,
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
addParams,
|
addParams,
|
||||||
removeParams
|
removeParams,
|
||||||
} from '@util/qs';
|
} from '@util/qs';
|
||||||
import { QSConfig } from '@types';
|
import { QSConfig } from '@types';
|
||||||
|
|
||||||
@@ -21,12 +21,12 @@ const EmptyStateControlsWrapper = styled.div`
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
|
||||||
& > :not(:first-child) {
|
& > :not(:first-child) {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
class ListHeader extends React.Component {
|
class ListHeader extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleSearch = this.handleSearch.bind(this);
|
this.handleSearch = this.handleSearch.bind(this);
|
||||||
@@ -35,7 +35,7 @@ class ListHeader extends React.Component {
|
|||||||
this.handleRemoveAll = this.handleRemoveAll.bind(this);
|
this.handleRemoveAll = this.handleRemoveAll.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSortOrder () {
|
getSortOrder() {
|
||||||
const { qsConfig, location } = this.props;
|
const { qsConfig, location } = this.props;
|
||||||
const queryParams = parseQueryString(qsConfig, location.search);
|
const queryParams = parseQueryString(qsConfig, location.search);
|
||||||
if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
|
if (queryParams.order_by && queryParams.order_by.startsWith('-')) {
|
||||||
@@ -44,45 +44,47 @@ class ListHeader extends React.Component {
|
|||||||
return [queryParams.order_by, 'ascending'];
|
return [queryParams.order_by, 'ascending'];
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch (key, value) {
|
handleSearch(key, value) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history, qsConfig } = this.props;
|
||||||
const { search } = history.location;
|
const { search } = history.location;
|
||||||
this.pushHistoryState(addParams(qsConfig, search, { [key]: value }));
|
this.pushHistoryState(addParams(qsConfig, search, { [key]: value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemove (key, value) {
|
handleRemove(key, value) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history, qsConfig } = this.props;
|
||||||
const { search } = history.location;
|
const { search } = history.location;
|
||||||
this.pushHistoryState(removeParams(qsConfig, search, { [key]: value }));
|
this.pushHistoryState(removeParams(qsConfig, search, { [key]: value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRemoveAll () {
|
handleRemoveAll() {
|
||||||
this.pushHistoryState(null);
|
this.pushHistoryState(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSort (key, order) {
|
handleSort(key, order) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history, qsConfig } = this.props;
|
||||||
const { search } = history.location;
|
const { search } = history.location;
|
||||||
this.pushHistoryState(addParams(qsConfig, search, {
|
this.pushHistoryState(
|
||||||
order_by: order === 'ascending' ? key : `-${key}`,
|
addParams(qsConfig, search, {
|
||||||
page: null,
|
order_by: order === 'ascending' ? key : `-${key}`,
|
||||||
}));
|
page: null,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pushHistoryState (params) {
|
pushHistoryState(params) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history, qsConfig } = this.props;
|
||||||
const { pathname } = history.location;
|
const { pathname } = history.location;
|
||||||
const encodedParams = encodeNonDefaultQueryString(qsConfig, params);
|
const encodedParams = encodeNonDefaultQueryString(qsConfig, params);
|
||||||
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
emptyStateControls,
|
emptyStateControls,
|
||||||
itemCount,
|
itemCount,
|
||||||
columns,
|
columns,
|
||||||
renderToolbar,
|
renderToolbar,
|
||||||
qsConfig
|
qsConfig,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const [orderBy, sortOrder] = this.getSortOrder();
|
const [orderBy, sortOrder] = this.getSortOrder();
|
||||||
return (
|
return (
|
||||||
@@ -124,17 +126,19 @@ class ListHeader extends React.Component {
|
|||||||
ListHeader.propTypes = {
|
ListHeader.propTypes = {
|
||||||
itemCount: PropTypes.number.isRequired,
|
itemCount: PropTypes.number.isRequired,
|
||||||
qsConfig: QSConfig.isRequired,
|
qsConfig: QSConfig.isRequired,
|
||||||
columns: arrayOf(shape({
|
columns: arrayOf(
|
||||||
name: string.isRequired,
|
shape({
|
||||||
key: string.isRequired,
|
name: string.isRequired,
|
||||||
isSortable: bool,
|
key: string.isRequired,
|
||||||
isSearchable: bool
|
isSortable: bool,
|
||||||
})).isRequired,
|
isSearchable: bool,
|
||||||
|
})
|
||||||
|
).isRequired,
|
||||||
renderToolbar: PropTypes.func,
|
renderToolbar: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
ListHeader.defaultProps = {
|
ListHeader.defaultProps = {
|
||||||
renderToolbar: (props) => (<DataListToolbar {...props} />),
|
renderToolbar: props => <DataListToolbar {...props} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withRouter(ListHeader);
|
export default withRouter(ListHeader);
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ describe('ListHeader', () => {
|
|||||||
<ListHeader
|
<ListHeader
|
||||||
itemCount={50}
|
itemCount={50}
|
||||||
qsConfig={qsConfig}
|
qsConfig={qsConfig}
|
||||||
columns={[{ name: 'foo', key: 'foo', isSearchable: true, isSortable: true }]}
|
columns={[
|
||||||
|
{ name: 'foo', key: 'foo', isSearchable: true, isSortable: true },
|
||||||
|
]}
|
||||||
renderToolbar={renderToolbarFn}
|
renderToolbar={renderToolbarFn}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -33,8 +35,11 @@ describe('ListHeader', () => {
|
|||||||
<ListHeader
|
<ListHeader
|
||||||
itemCount={7}
|
itemCount={7}
|
||||||
qsConfig={qsConfig}
|
qsConfig={qsConfig}
|
||||||
columns={[{ name: 'name', key: 'name', isSearchable: true, isSortable: true }]}
|
columns={[
|
||||||
/>, { context: { router: { history } } }
|
{ name: 'name', key: 'name', isSearchable: true, isSortable: true },
|
||||||
|
]}
|
||||||
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
);
|
);
|
||||||
|
|
||||||
const toolbar = wrapper.find('DataListToolbar');
|
const toolbar = wrapper.find('DataListToolbar');
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import DataListToolbar from '@components/DataListToolbar';
|
|||||||
import {
|
import {
|
||||||
encodeNonDefaultQueryString,
|
encodeNonDefaultQueryString,
|
||||||
parseQueryString,
|
parseQueryString,
|
||||||
addParams
|
addParams,
|
||||||
} from '@util/qs';
|
} from '@util/qs';
|
||||||
import { pluralize, ucFirst } from '@util/strings';
|
import { pluralize, ucFirst } from '@util/strings';
|
||||||
|
|
||||||
@@ -24,32 +24,32 @@ import { QSConfig } from '@types';
|
|||||||
import PaginatedDataListItem from './PaginatedDataListItem';
|
import PaginatedDataListItem from './PaginatedDataListItem';
|
||||||
|
|
||||||
class PaginatedDataList extends React.Component {
|
class PaginatedDataList extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.handleSetPage = this.handleSetPage.bind(this);
|
this.handleSetPage = this.handleSetPage.bind(this);
|
||||||
this.handleSetPageSize = this.handleSetPageSize.bind(this);
|
this.handleSetPageSize = this.handleSetPageSize.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSetPage (event, pageNumber) {
|
handleSetPage(event, pageNumber) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history, qsConfig } = this.props;
|
||||||
const { search } = history.location;
|
const { search } = history.location;
|
||||||
this.pushHistoryState(addParams(qsConfig, search, { page: pageNumber }));
|
this.pushHistoryState(addParams(qsConfig, search, { page: pageNumber }));
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSetPageSize (event, pageSize) {
|
handleSetPageSize(event, pageSize) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history, qsConfig } = this.props;
|
||||||
const { search } = history.location;
|
const { search } = history.location;
|
||||||
this.pushHistoryState(addParams(qsConfig, search, { page_size: pageSize }));
|
this.pushHistoryState(addParams(qsConfig, search, { page_size: pageSize }));
|
||||||
}
|
}
|
||||||
|
|
||||||
pushHistoryState (params) {
|
pushHistoryState(params) {
|
||||||
const { history, qsConfig } = this.props;
|
const { history, qsConfig } = this.props;
|
||||||
const { pathname } = history.location;
|
const { pathname } = history.location;
|
||||||
const encodedParams = encodeNonDefaultQueryString(qsConfig, params);
|
const encodedParams = encodeNonDefaultQueryString(qsConfig, params);
|
||||||
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
history.push(encodedParams ? `${pathname}?${encodedParams}` : pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
contentError,
|
contentError,
|
||||||
hasContentLoading,
|
hasContentLoading,
|
||||||
@@ -66,25 +66,42 @@ class PaginatedDataList extends React.Component {
|
|||||||
i18n,
|
i18n,
|
||||||
renderToolbar,
|
renderToolbar,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const columns = toolbarColumns.length ? toolbarColumns : [{ name: i18n._(t`Name`), key: 'name', isSortable: true, isSearchable: true }];
|
const columns = toolbarColumns.length
|
||||||
|
? toolbarColumns
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
const queryParams = parseQueryString(qsConfig, location.search);
|
const queryParams = parseQueryString(qsConfig, location.search);
|
||||||
|
|
||||||
const itemDisplayName = ucFirst(pluralize(itemName));
|
const itemDisplayName = ucFirst(pluralize(itemName));
|
||||||
const itemDisplayNamePlural = ucFirst(itemNamePlural || pluralize(itemName));
|
const itemDisplayNamePlural = ucFirst(
|
||||||
|
itemNamePlural || pluralize(itemName)
|
||||||
|
);
|
||||||
|
|
||||||
const dataListLabel = i18n._(t`${itemDisplayName} List`);
|
const dataListLabel = i18n._(t`${itemDisplayName} List`);
|
||||||
const emptyContentMessage = i18n._(t`Please add ${itemDisplayNamePlural} to populate this list `);
|
const emptyContentMessage = i18n._(
|
||||||
|
t`Please add ${itemDisplayNamePlural} to populate this list `
|
||||||
|
);
|
||||||
const emptyContentTitle = i18n._(t`No ${itemDisplayNamePlural} Found `);
|
const emptyContentTitle = i18n._(t`No ${itemDisplayNamePlural} Found `);
|
||||||
|
|
||||||
let Content;
|
let Content;
|
||||||
if (hasContentLoading && items.length <= 0) {
|
if (hasContentLoading && items.length <= 0) {
|
||||||
Content = (<ContentLoading />);
|
Content = <ContentLoading />;
|
||||||
} else if (contentError) {
|
} else if (contentError) {
|
||||||
Content = (<ContentError error={contentError} />);
|
Content = <ContentError error={contentError} />;
|
||||||
} else if (items.length <= 0) {
|
} else if (items.length <= 0) {
|
||||||
Content = (<ContentEmpty title={emptyContentTitle} message={emptyContentMessage} />);
|
Content = (
|
||||||
|
<ContentEmpty title={emptyContentTitle} message={emptyContentMessage} />
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
Content = (<DataList aria-label={dataListLabel}>{items.map(renderItem)}</DataList>);
|
Content = (
|
||||||
|
<DataList aria-label={dataListLabel}>{items.map(renderItem)}</DataList>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.length <= 0) {
|
if (items.length <= 0) {
|
||||||
@@ -115,12 +132,16 @@ class PaginatedDataList extends React.Component {
|
|||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
page={queryParams.page || 1}
|
page={queryParams.page || 1}
|
||||||
perPage={queryParams.page_size}
|
perPage={queryParams.page_size}
|
||||||
perPageOptions={showPageSizeOptions ? [
|
perPageOptions={
|
||||||
{ title: '5', value: 5 },
|
showPageSizeOptions
|
||||||
{ title: '10', value: 10 },
|
? [
|
||||||
{ title: '20', value: 20 },
|
{ title: '5', value: 5 },
|
||||||
{ title: '50', value: 50 }
|
{ title: '10', value: 10 },
|
||||||
] : []}
|
{ title: '20', value: 20 },
|
||||||
|
{ title: '50', value: 50 },
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
}
|
||||||
onSetPage={this.handleSetPage}
|
onSetPage={this.handleSetPage}
|
||||||
onPerPageSelect={this.handleSetPageSize}
|
onPerPageSelect={this.handleSetPageSize}
|
||||||
/>
|
/>
|
||||||
@@ -142,11 +163,13 @@ PaginatedDataList.propTypes = {
|
|||||||
itemNamePlural: PropTypes.string,
|
itemNamePlural: PropTypes.string,
|
||||||
qsConfig: QSConfig.isRequired,
|
qsConfig: QSConfig.isRequired,
|
||||||
renderItem: PropTypes.func,
|
renderItem: PropTypes.func,
|
||||||
toolbarColumns: arrayOf(shape({
|
toolbarColumns: arrayOf(
|
||||||
name: string.isRequired,
|
shape({
|
||||||
key: string.isRequired,
|
name: string.isRequired,
|
||||||
isSortable: bool,
|
key: string.isRequired,
|
||||||
})),
|
isSortable: bool,
|
||||||
|
})
|
||||||
|
),
|
||||||
showPageSizeOptions: PropTypes.bool,
|
showPageSizeOptions: PropTypes.bool,
|
||||||
renderToolbar: PropTypes.func,
|
renderToolbar: PropTypes.func,
|
||||||
hasContentLoading: PropTypes.bool,
|
hasContentLoading: PropTypes.bool,
|
||||||
@@ -160,8 +183,8 @@ PaginatedDataList.defaultProps = {
|
|||||||
itemName: 'item',
|
itemName: 'item',
|
||||||
itemNamePlural: '',
|
itemNamePlural: '',
|
||||||
showPageSizeOptions: true,
|
showPageSizeOptions: true,
|
||||||
renderItem: (item) => (<PaginatedDataListItem key={item.id} item={item} />),
|
renderItem: item => <PaginatedDataListItem key={item.id} item={item} />,
|
||||||
renderToolbar: (props) => (<DataListToolbar {...props} />),
|
renderToolbar: props => <DataListToolbar {...props} />,
|
||||||
};
|
};
|
||||||
|
|
||||||
export { PaginatedDataList as _PaginatedDataList };
|
export { PaginatedDataList as _PaginatedDataList };
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ describe('<PaginatedDataList />', () => {
|
|||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
}}
|
}}
|
||||||
qsConfig={qsConfig}
|
qsConfig={qsConfig}
|
||||||
/>, { context: { router: { history } } }
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
);
|
);
|
||||||
|
|
||||||
const pagination = wrapper.find('Pagination');
|
const pagination = wrapper.find('Pagination');
|
||||||
@@ -77,7 +78,8 @@ describe('<PaginatedDataList />', () => {
|
|||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
}}
|
}}
|
||||||
qsConfig={qsConfig}
|
qsConfig={qsConfig}
|
||||||
/>, { context: { router: { history } } }
|
/>,
|
||||||
|
{ context: { router: { history } } }
|
||||||
);
|
);
|
||||||
|
|
||||||
const pagination = wrapper.find('Pagination');
|
const pagination = wrapper.find('Pagination');
|
||||||
|
|||||||
@@ -10,11 +10,9 @@ import {
|
|||||||
DropdownItem,
|
DropdownItem,
|
||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
TextInput as PFTextInput
|
TextInput as PFTextInput,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import {
|
import { SearchIcon } from '@patternfly/react-icons';
|
||||||
SearchIcon
|
|
||||||
} from '@patternfly/react-icons';
|
|
||||||
|
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
@@ -29,7 +27,8 @@ const Button = styled(PFButton)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Dropdown = styled(PFDropdown)`
|
const Dropdown = styled(PFDropdown)`
|
||||||
&&& { /* Higher specificity required because we are selecting unclassed elements */
|
&&& {
|
||||||
|
/* Higher specificity required because we are selecting unclassed elements */
|
||||||
> button {
|
> button {
|
||||||
min-height: 30px;
|
min-height: 30px;
|
||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
@@ -37,17 +36,19 @@ const Dropdown = styled(PFDropdown)`
|
|||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
|
||||||
> span { /* text element */
|
> span {
|
||||||
|
/* text element */
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
> svg { /* caret icon */
|
> svg {
|
||||||
|
/* caret icon */
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding-top: 3px;
|
padding-top: 3px;
|
||||||
padding-left: 3px;
|
padding-left: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const NoOptionDropdown = styled.div`
|
const NoOptionDropdown = styled.div`
|
||||||
@@ -61,7 +62,7 @@ const InputFormGroup = styled(FormGroup)`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
class Search extends React.Component {
|
class Search extends React.Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { sortedColumnKey } = this.props;
|
const { sortedColumnKey } = this.props;
|
||||||
@@ -77,11 +78,11 @@ class Search extends React.Component {
|
|||||||
this.handleSearch = this.handleSearch.bind(this);
|
this.handleSearch = this.handleSearch.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDropdownToggle (isSearchDropdownOpen) {
|
handleDropdownToggle(isSearchDropdownOpen) {
|
||||||
this.setState({ isSearchDropdownOpen });
|
this.setState({ isSearchDropdownOpen });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDropdownSelect ({ target }) {
|
handleDropdownSelect({ target }) {
|
||||||
const { columns } = this.props;
|
const { columns } = this.props;
|
||||||
const { innerText } = target;
|
const { innerText } = target;
|
||||||
|
|
||||||
@@ -89,7 +90,7 @@ class Search extends React.Component {
|
|||||||
this.setState({ isSearchDropdownOpen: false, searchKey });
|
this.setState({ isSearchDropdownOpen: false, searchKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch (e) {
|
handleSearch(e) {
|
||||||
// keeps page from fully reloading
|
// keeps page from fully reloading
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
@@ -102,22 +103,17 @@ class Search extends React.Component {
|
|||||||
this.setState({ searchValue: '' });
|
this.setState({ searchValue: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearchInputChange (searchValue) {
|
handleSearchInputChange(searchValue) {
|
||||||
this.setState({ searchValue });
|
this.setState({ searchValue });
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { up } = DropdownPosition;
|
const { up } = DropdownPosition;
|
||||||
const {
|
const { columns, i18n } = this.props;
|
||||||
columns,
|
const { isSearchDropdownOpen, searchKey, searchValue } = this.state;
|
||||||
i18n
|
const { name: searchColumnName } = columns.find(
|
||||||
} = this.props;
|
({ key }) => key === searchKey
|
||||||
const {
|
);
|
||||||
isSearchDropdownOpen,
|
|
||||||
searchKey,
|
|
||||||
searchValue,
|
|
||||||
} = this.state;
|
|
||||||
const { name: searchColumnName } = columns.find(({ key }) => key === searchKey);
|
|
||||||
|
|
||||||
const searchDropdownItems = columns
|
const searchDropdownItems = columns
|
||||||
.filter(({ key, isSearchable }) => isSearchable && key !== searchKey)
|
.filter(({ key, isSearchable }) => isSearchable && key !== searchKey)
|
||||||
@@ -133,32 +129,38 @@ class Search extends React.Component {
|
|||||||
{searchDropdownItems.length > 0 ? (
|
{searchDropdownItems.length > 0 ? (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
fieldId="searchKeyDropdown"
|
fieldId="searchKeyDropdown"
|
||||||
label={(<span className="pf-screen-reader">{i18n._(t`Search key dropdown`)}</span>)}
|
label={
|
||||||
|
<span className="pf-screen-reader">
|
||||||
|
{i18n._(t`Search key dropdown`)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
onToggle={this.handleDropdownToggle}
|
onToggle={this.handleDropdownToggle}
|
||||||
onSelect={this.handleDropdownSelect}
|
onSelect={this.handleDropdownSelect}
|
||||||
direction={up}
|
direction={up}
|
||||||
isOpen={isSearchDropdownOpen}
|
isOpen={isSearchDropdownOpen}
|
||||||
toggle={(
|
toggle={
|
||||||
<DropdownToggle
|
<DropdownToggle
|
||||||
id="awx-search"
|
id="awx-search"
|
||||||
onToggle={this.handleDropdownToggle}
|
onToggle={this.handleDropdownToggle}
|
||||||
>
|
>
|
||||||
{searchColumnName}
|
{searchColumnName}
|
||||||
</DropdownToggle>
|
</DropdownToggle>
|
||||||
)}
|
}
|
||||||
dropdownItems={searchDropdownItems}
|
dropdownItems={searchDropdownItems}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
) : (
|
) : (
|
||||||
<NoOptionDropdown>
|
<NoOptionDropdown>{searchColumnName}</NoOptionDropdown>
|
||||||
{searchColumnName}
|
|
||||||
</NoOptionDropdown>
|
|
||||||
)}
|
)}
|
||||||
<InputFormGroup
|
<InputFormGroup
|
||||||
fieldId="searchValueTextInput"
|
fieldId="searchValueTextInput"
|
||||||
label={(<span className="pf-screen-reader">{i18n._(t`Search value text input`)}</span>)}
|
label={
|
||||||
|
<span className="pf-screen-reader">
|
||||||
|
{i18n._(t`Search value text input`)}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="search"
|
type="search"
|
||||||
@@ -190,7 +192,7 @@ Search.propTypes = {
|
|||||||
|
|
||||||
Search.defaultProps = {
|
Search.defaultProps = {
|
||||||
onSearch: null,
|
onSearch: null,
|
||||||
sortedColumnKey: 'name'
|
sortedColumnKey: 'name',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(Search);
|
export default withI18n()(Search);
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ describe('<Search />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it triggers the expected callbacks', () => {
|
test('it triggers the expected callbacks', () => {
|
||||||
const columns = [{ name: 'Name', key: 'name', isSortable: true, isSearchable: true }];
|
const columns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||||
|
];
|
||||||
|
|
||||||
const searchBtn = 'button[aria-label="Search submit button"]';
|
const searchBtn = 'button[aria-label="Search submit button"]';
|
||||||
const searchTextInput = 'input[aria-label="Search text input"]';
|
const searchTextInput = 'input[aria-label="Search text input"]';
|
||||||
@@ -20,11 +22,7 @@ describe('<Search />', () => {
|
|||||||
const onSearch = jest.fn();
|
const onSearch = jest.fn();
|
||||||
|
|
||||||
search = mountWithContexts(
|
search = mountWithContexts(
|
||||||
<Search
|
<Search sortedColumnKey="name" columns={columns} onSearch={onSearch} />
|
||||||
sortedColumnKey="name"
|
|
||||||
columns={columns}
|
|
||||||
onSearch={onSearch}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
search.find(searchTextInput).instance().value = 'test-321';
|
search.find(searchTextInput).instance().value = 'test-321';
|
||||||
@@ -36,14 +34,12 @@ describe('<Search />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('handleDropdownToggle properly updates state', async () => {
|
test('handleDropdownToggle properly updates state', async () => {
|
||||||
const columns = [{ name: 'Name', key: 'name', isSortable: true, isSearchable: true }];
|
const columns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||||
|
];
|
||||||
const onSearch = jest.fn();
|
const onSearch = jest.fn();
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<Search
|
<Search sortedColumnKey="name" columns={columns} onSearch={onSearch} />
|
||||||
sortedColumnKey="name"
|
|
||||||
columns={columns}
|
|
||||||
onSearch={onSearch}
|
|
||||||
/>
|
|
||||||
).find('Search');
|
).find('Search');
|
||||||
expect(wrapper.state('isSearchDropdownOpen')).toEqual(false);
|
expect(wrapper.state('isSearchDropdownOpen')).toEqual(false);
|
||||||
wrapper.instance().handleDropdownToggle(true);
|
wrapper.instance().handleDropdownToggle(true);
|
||||||
@@ -53,18 +49,21 @@ describe('<Search />', () => {
|
|||||||
test('handleDropdownSelect properly updates state', async () => {
|
test('handleDropdownSelect properly updates state', async () => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||||
{ name: 'Description', key: 'description', isSortable: true, isSearchable: true }
|
{
|
||||||
|
name: 'Description',
|
||||||
|
key: 'description',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const onSearch = jest.fn();
|
const onSearch = jest.fn();
|
||||||
const wrapper = mountWithContexts(
|
const wrapper = mountWithContexts(
|
||||||
<Search
|
<Search sortedColumnKey="name" columns={columns} onSearch={onSearch} />
|
||||||
sortedColumnKey="name"
|
|
||||||
columns={columns}
|
|
||||||
onSearch={onSearch}
|
|
||||||
/>
|
|
||||||
).find('Search');
|
).find('Search');
|
||||||
expect(wrapper.state('searchKey')).toEqual('name');
|
expect(wrapper.state('searchKey')).toEqual('name');
|
||||||
wrapper.instance().handleDropdownSelect({ target: { innerText: 'Description' } });
|
wrapper
|
||||||
|
.instance()
|
||||||
|
.handleDropdownSelect({ target: { innerText: 'Description' } });
|
||||||
expect(wrapper.state('searchKey')).toEqual('description');
|
expect(wrapper.state('searchKey')).toEqual('description');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ describe('<Sort />', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('it triggers the expected callbacks', () => {
|
test('it triggers the expected callbacks', () => {
|
||||||
const columns = [{ name: 'Name', key: 'name', isSortable: true, isSearchable: true }];
|
const columns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true, isSearchable: true },
|
||||||
|
];
|
||||||
|
|
||||||
const sortBtn = 'button[aria-label="Sort"]';
|
const sortBtn = 'button[aria-label="Sort"]';
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ describe('<Sort />', () => {
|
|||||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
{ name: 'Baz', key: 'baz' }
|
{ name: 'Baz', key: 'baz' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
@@ -62,7 +64,7 @@ describe('<Sort />', () => {
|
|||||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
{ name: 'Baz', key: 'baz' }
|
{ name: 'Baz', key: 'baz' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
@@ -86,7 +88,7 @@ describe('<Sort />', () => {
|
|||||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
{ name: 'Baz', key: 'baz' }
|
{ name: 'Baz', key: 'baz' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
@@ -109,7 +111,7 @@ describe('<Sort />', () => {
|
|||||||
{ name: 'Foo', key: 'foo', isSortable: true },
|
{ name: 'Foo', key: 'foo', isSortable: true },
|
||||||
{ name: 'Bar', key: 'bar', isSortable: true },
|
{ name: 'Bar', key: 'bar', isSortable: true },
|
||||||
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
{ name: 'Bakery', key: 'bakery', isSortable: true },
|
||||||
{ name: 'Baz', key: 'baz' }
|
{ name: 'Baz', key: 'baz' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
@@ -133,8 +135,12 @@ describe('<Sort />', () => {
|
|||||||
const downAlphaIconSelector = 'SortAlphaDownIcon';
|
const downAlphaIconSelector = 'SortAlphaDownIcon';
|
||||||
const upAlphaIconSelector = 'SortAlphaUpIcon';
|
const upAlphaIconSelector = 'SortAlphaUpIcon';
|
||||||
|
|
||||||
const numericColumns = [{ name: 'ID', key: 'id', isSortable: true, isNumeric: true }];
|
const numericColumns = [
|
||||||
const alphaColumns = [{ name: 'Name', key: 'name', isSortable: true, isNumeric: false }];
|
{ name: 'ID', key: 'id', isSortable: true, isNumeric: true },
|
||||||
|
];
|
||||||
|
const alphaColumns = [
|
||||||
|
{ name: 'Name', key: 'name', isSortable: true, isNumeric: false },
|
||||||
|
];
|
||||||
const onSort = jest.fn();
|
const onSort = jest.fn();
|
||||||
|
|
||||||
sort = mountWithContexts(
|
sort = mountWithContexts(
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ import React, { Component } from 'react';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import { Card, PageSection, PageSectionVariants } from '@patternfly/react-core';
|
||||||
Card,
|
|
||||||
PageSection,
|
|
||||||
PageSectionVariants,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import { UnifiedJobsAPI } from '@api';
|
import { UnifiedJobsAPI } from '@api';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import DatalistToolbar from '@components/DataListToolbar';
|
import DatalistToolbar from '@components/DataListToolbar';
|
||||||
import PaginatedDataList, {
|
import PaginatedDataList, {
|
||||||
ToolbarDeleteButton
|
ToolbarDeleteButton,
|
||||||
} from '@components/PaginatedDataList';
|
} from '@components/PaginatedDataList';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
@@ -26,12 +22,12 @@ const QS_CONFIG = getQSConfig('job', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
class JobList extends Component {
|
class JobList extends Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
hasContentError: false,
|
contentError: null,
|
||||||
deletionError: false,
|
deletionError: false,
|
||||||
selected: [],
|
selected: [],
|
||||||
jobs: [],
|
jobs: [],
|
||||||
@@ -44,28 +40,28 @@ class JobList extends Component {
|
|||||||
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.loadJobs();
|
this.loadJobs();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
if (location !== prevProps.location) {
|
if (location !== prevProps.location) {
|
||||||
this.loadJobs();
|
this.loadJobs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteErrorClose () {
|
handleDeleteErrorClose() {
|
||||||
this.setState({ deletionError: false });
|
this.setState({ deletionError: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectAll (isSelected) {
|
handleSelectAll(isSelected) {
|
||||||
const { jobs } = this.state;
|
const { jobs } = this.state;
|
||||||
const selected = isSelected ? [...jobs] : [];
|
const selected = isSelected ? [...jobs] : [];
|
||||||
this.setState({ selected });
|
this.setState({ selected });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelect (item) {
|
handleSelect(item) {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
if (selected.some(s => s.id === item.id)) {
|
if (selected.some(s => s.id === item.id)) {
|
||||||
this.setState({ selected: selected.filter(s => s.id !== item.id) });
|
this.setState({ selected: selected.filter(s => s.id !== item.id) });
|
||||||
@@ -74,7 +70,7 @@ class JobList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDelete () {
|
async handleDelete() {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
this.setState({ hasContentLoading: true, deletionError: false });
|
this.setState({ hasContentLoading: true, deletionError: false });
|
||||||
try {
|
try {
|
||||||
@@ -86,38 +82,37 @@ class JobList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadJobs () {
|
async loadJobs() {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
|
||||||
this.setState({ hasContentError: false, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
try {
|
try {
|
||||||
const { data: { count, results } } = await UnifiedJobsAPI.read(params);
|
const {
|
||||||
|
data: { count, results },
|
||||||
|
} = await UnifiedJobsAPI.read(params);
|
||||||
this.setState({
|
this.setState({
|
||||||
itemCount: count,
|
itemCount: count,
|
||||||
jobs: results,
|
jobs: results,
|
||||||
selected: [],
|
selected: [],
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ hasContentError: true });
|
this.setState({ contentError: err });
|
||||||
} finally {
|
} finally {
|
||||||
this.setState({ hasContentLoading: false });
|
this.setState({ hasContentLoading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
hasContentError,
|
contentError,
|
||||||
hasContentLoading,
|
hasContentLoading,
|
||||||
deletionError,
|
deletionError,
|
||||||
jobs,
|
jobs,
|
||||||
itemCount,
|
itemCount,
|
||||||
selected,
|
selected,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const { match, i18n } = this.props;
|
||||||
match,
|
|
||||||
i18n
|
|
||||||
} = this.props;
|
|
||||||
const { medium } = PageSectionVariants;
|
const { medium } = PageSectionVariants;
|
||||||
const isAllSelected = selected.length === jobs.length;
|
const isAllSelected = selected.length === jobs.length;
|
||||||
const itemName = i18n._(t`Job`);
|
const itemName = i18n._(t`Job`);
|
||||||
@@ -125,17 +120,27 @@ class JobList extends Component {
|
|||||||
<PageSection variant={medium}>
|
<PageSection variant={medium}>
|
||||||
<Card>
|
<Card>
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
hasContentError={hasContentError}
|
contentError={contentError}
|
||||||
hasContentLoading={hasContentLoading}
|
hasContentLoading={hasContentLoading}
|
||||||
items={jobs}
|
items={jobs}
|
||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
itemName={itemName}
|
itemName={itemName}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
toolbarColumns={[
|
toolbarColumns={[
|
||||||
{ name: i18n._(t`Name`), key: 'name', isSortable: true, isSearchable: true },
|
{
|
||||||
{ name: i18n._(t`Finished`), key: 'finished', isSortable: true, isNumeric: true },
|
name: i18n._(t`Name`),
|
||||||
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Finished`),
|
||||||
|
key: 'finished',
|
||||||
|
isSortable: true,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
renderToolbar={(props) => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
showSelectAll
|
||||||
@@ -148,11 +153,11 @@ class JobList extends Component {
|
|||||||
onDelete={this.handleDelete}
|
onDelete={this.handleDelete}
|
||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
itemName={itemName}
|
itemName={itemName}
|
||||||
/>
|
/>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderItem={(job) => (
|
renderItem={job => (
|
||||||
<JobListItem
|
<JobListItem
|
||||||
key={job.id}
|
key={job.id}
|
||||||
value={job.name}
|
value={job.name}
|
||||||
|
|||||||
@@ -7,12 +7,10 @@ import { OrganizationsAPI, TeamsAPI, UsersAPI } from '@api';
|
|||||||
import AddResourceRole from '@components/AddRole/AddResourceRole';
|
import AddResourceRole from '@components/AddRole/AddResourceRole';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import DataListToolbar from '@components/DataListToolbar';
|
import DataListToolbar from '@components/DataListToolbar';
|
||||||
import PaginatedDataList, { ToolbarAddButton } from '@components/PaginatedDataList';
|
import PaginatedDataList, {
|
||||||
import {
|
ToolbarAddButton,
|
||||||
getQSConfig,
|
} from '@components/PaginatedDataList';
|
||||||
encodeQueryString,
|
import { getQSConfig, encodeQueryString, parseQueryString } from '@util/qs';
|
||||||
parseQueryString
|
|
||||||
} from '@util/qs';
|
|
||||||
import { Organization } from '@types';
|
import { Organization } from '@types';
|
||||||
|
|
||||||
import DeleteRoleConfirmationModal from './DeleteRoleConfirmationModal';
|
import DeleteRoleConfirmationModal from './DeleteRoleConfirmationModal';
|
||||||
@@ -29,11 +27,11 @@ class OrganizationAccess extends React.Component {
|
|||||||
organization: Organization.isRequired,
|
organization: Organization.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
accessRecords: [],
|
accessRecords: [],
|
||||||
hasContentError: false,
|
contentError: null,
|
||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
hasDeletionError: false,
|
hasDeletionError: false,
|
||||||
deletionRecord: null,
|
deletionRecord: null,
|
||||||
@@ -51,11 +49,11 @@ class OrganizationAccess extends React.Component {
|
|||||||
this.handleDeleteOpen = this.handleDeleteOpen.bind(this);
|
this.handleDeleteOpen = this.handleDeleteOpen.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.loadAccessList();
|
this.loadAccessList();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
|
|
||||||
const prevParams = parseQueryString(QS_CONFIG, prevProps.location.search);
|
const prevParams = parseQueryString(QS_CONFIG, prevProps.location.search);
|
||||||
@@ -66,43 +64,40 @@ class OrganizationAccess extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadAccessList () {
|
async loadAccessList() {
|
||||||
const { organization, location } = this.props;
|
const { organization, location } = this.props;
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
|
||||||
this.setState({ hasContentError: false, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: {
|
data: { results: accessRecords = [], count: itemCount = 0 },
|
||||||
results: accessRecords = [],
|
|
||||||
count: itemCount = 0
|
|
||||||
}
|
|
||||||
} = await OrganizationsAPI.readAccessList(organization.id, params);
|
} = await OrganizationsAPI.readAccessList(organization.id, params);
|
||||||
this.setState({ itemCount, accessRecords });
|
this.setState({ itemCount, accessRecords });
|
||||||
} catch (error) {
|
} catch (err) {
|
||||||
this.setState({ hasContentError: true });
|
this.setState({ cotentError: err });
|
||||||
} finally {
|
} finally {
|
||||||
this.setState({ hasContentLoading: false });
|
this.setState({ hasContentLoading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteOpen (deletionRole, deletionRecord) {
|
handleDeleteOpen(deletionRole, deletionRecord) {
|
||||||
this.setState({ deletionRole, deletionRecord });
|
this.setState({ deletionRole, deletionRecord });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteCancel () {
|
handleDeleteCancel() {
|
||||||
this.setState({ deletionRole: null, deletionRecord: null });
|
this.setState({ deletionRole: null, deletionRecord: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteErrorClose () {
|
handleDeleteErrorClose() {
|
||||||
this.setState({
|
this.setState({
|
||||||
hasDeletionError: false,
|
hasDeletionError: false,
|
||||||
deletionRecord: null,
|
deletionRecord: null,
|
||||||
deletionRole: null
|
deletionRole: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleDeleteConfirm () {
|
async handleDeleteConfirm() {
|
||||||
const { deletionRole, deletionRecord } = this.state;
|
const { deletionRole, deletionRecord } = this.state;
|
||||||
|
|
||||||
if (!deletionRole || !deletionRecord) {
|
if (!deletionRole || !deletionRecord) {
|
||||||
@@ -111,7 +106,10 @@ class OrganizationAccess extends React.Component {
|
|||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
if (typeof deletionRole.team_id !== 'undefined') {
|
if (typeof deletionRole.team_id !== 'undefined') {
|
||||||
promise = TeamsAPI.disassociateRole(deletionRole.team_id, deletionRole.id);
|
promise = TeamsAPI.disassociateRole(
|
||||||
|
deletionRole.team_id,
|
||||||
|
deletionRole.id
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
promise = UsersAPI.disassociateRole(deletionRecord.id, deletionRole.id);
|
promise = UsersAPI.disassociateRole(deletionRecord.id, deletionRole.id);
|
||||||
}
|
}
|
||||||
@@ -121,34 +119,34 @@ class OrganizationAccess extends React.Component {
|
|||||||
await promise.then(this.loadAccessList);
|
await promise.then(this.loadAccessList);
|
||||||
this.setState({
|
this.setState({
|
||||||
deletionRole: null,
|
deletionRole: null,
|
||||||
deletionRecord: null
|
deletionRecord: null,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.setState({
|
this.setState({
|
||||||
hasContentLoading: false,
|
hasContentLoading: false,
|
||||||
hasDeletionError: true
|
hasDeletionError: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddClose () {
|
handleAddClose() {
|
||||||
this.setState({ isAddModalOpen: false });
|
this.setState({ isAddModalOpen: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddOpen () {
|
handleAddOpen() {
|
||||||
this.setState({ isAddModalOpen: true });
|
this.setState({ isAddModalOpen: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddSuccess () {
|
handleAddSuccess() {
|
||||||
this.setState({ isAddModalOpen: false });
|
this.setState({ isAddModalOpen: false });
|
||||||
this.loadAccessList();
|
this.loadAccessList();
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { organization, i18n } = this.props;
|
const { organization, i18n } = this.props;
|
||||||
const {
|
const {
|
||||||
accessRecords,
|
accessRecords,
|
||||||
hasContentError,
|
contentError,
|
||||||
hasContentLoading,
|
hasContentLoading,
|
||||||
deletionRole,
|
deletionRole,
|
||||||
deletionRecord,
|
deletionRecord,
|
||||||
@@ -157,28 +155,51 @@ class OrganizationAccess extends React.Component {
|
|||||||
isAddModalOpen,
|
isAddModalOpen,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const canEdit = organization.summary_fields.user_capabilities.edit;
|
const canEdit = organization.summary_fields.user_capabilities.edit;
|
||||||
const isDeleteModalOpen = !hasContentLoading && !hasDeletionError && deletionRole;
|
const isDeleteModalOpen =
|
||||||
|
!hasContentLoading && !hasDeletionError && deletionRole;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
hasContentError={hasContentError}
|
error={contentError}
|
||||||
hasContentLoading={hasContentLoading}
|
hasContentLoading={hasContentLoading}
|
||||||
items={accessRecords}
|
items={accessRecords}
|
||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
itemName="role"
|
itemName="role"
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
toolbarColumns={[
|
toolbarColumns={[
|
||||||
{ name: i18n._(t`First Name`), key: 'first_name', isSortable: true, isSearchable: true },
|
{
|
||||||
{ name: i18n._(t`Username`), key: 'username', isSortable: true, isSearchable: true },
|
name: i18n._(t`First Name`),
|
||||||
{ name: i18n._(t`Last Name`), key: 'last_name', isSortable: true, isSearchable: true },
|
key: 'first_name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Username`),
|
||||||
|
key: 'username',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Last Name`),
|
||||||
|
key: 'last_name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
renderToolbar={(props) => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
additionalControls={canEdit ? [
|
additionalControls={
|
||||||
<ToolbarAddButton key="add" onClick={this.handleAddOpen} />
|
canEdit
|
||||||
] : null}
|
? [
|
||||||
|
<ToolbarAddButton
|
||||||
|
key="add"
|
||||||
|
onClick={this.handleAddOpen}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderItem={accessRecord => (
|
renderItem={accessRecord => (
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ describe('<OrganizationAccess />', () => {
|
|||||||
expect(wrapper.find('OrganizationAccess').state('hasContentLoading')).toBe(
|
expect(wrapper.find('OrganizationAccess').state('hasContentLoading')).toBe(
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
expect(wrapper.find('OrganizationAccess').state('hasContentError')).toBe(false);
|
expect(wrapper.find('OrganizationAccess').state('contentError')).toBe(null);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<WithI18n
|
<WithI18n
|
||||||
hasContentError={false}
|
error={null}
|
||||||
hasContentLoading={true}
|
hasContentLoading={true}
|
||||||
itemCount={0}
|
itemCount={0}
|
||||||
itemName="role"
|
itemName="role"
|
||||||
@@ -83,7 +83,7 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
|||||||
withHash={true}
|
withHash={true}
|
||||||
>
|
>
|
||||||
<withRouter(PaginatedDataList)
|
<withRouter(PaginatedDataList)
|
||||||
hasContentError={false}
|
error={null}
|
||||||
hasContentLoading={true}
|
hasContentLoading={true}
|
||||||
i18n={"/i18n/"}
|
i18n={"/i18n/"}
|
||||||
itemCount={0}
|
itemCount={0}
|
||||||
@@ -130,7 +130,8 @@ exports[`<OrganizationAccess /> initially renders succesfully 1`] = `
|
|||||||
>
|
>
|
||||||
<Route>
|
<Route>
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
hasContentError={false}
|
contentError={null}
|
||||||
|
error={null}
|
||||||
hasContentLoading={true}
|
hasContentLoading={true}
|
||||||
history={"/history/"}
|
history={"/history/"}
|
||||||
i18n={"/i18n/"}
|
i18n={"/i18n/"}
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import React, { Component, Fragment } from 'react';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import { Card, PageSection, PageSectionVariants } from '@patternfly/react-core';
|
||||||
Card,
|
|
||||||
PageSection,
|
|
||||||
PageSectionVariants,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import { OrganizationsAPI } from '@api';
|
import { OrganizationsAPI } from '@api';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
@@ -26,12 +22,12 @@ const QS_CONFIG = getQSConfig('organization', {
|
|||||||
});
|
});
|
||||||
|
|
||||||
class OrganizationsList extends Component {
|
class OrganizationsList extends Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
hasContentError: false,
|
contentError: null,
|
||||||
hasDeletionError: false,
|
hasDeletionError: false,
|
||||||
organizations: [],
|
organizations: [],
|
||||||
selected: [],
|
selected: [],
|
||||||
@@ -46,25 +42,25 @@ class OrganizationsList extends Component {
|
|||||||
this.loadOrganizations = this.loadOrganizations.bind(this);
|
this.loadOrganizations = this.loadOrganizations.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.loadOrganizations();
|
this.loadOrganizations();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
if (location !== prevProps.location) {
|
if (location !== prevProps.location) {
|
||||||
this.loadOrganizations();
|
this.loadOrganizations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectAll (isSelected) {
|
handleSelectAll(isSelected) {
|
||||||
const { organizations } = this.state;
|
const { organizations } = this.state;
|
||||||
|
|
||||||
const selected = isSelected ? [...organizations] : [];
|
const selected = isSelected ? [...organizations] : [];
|
||||||
this.setState({ selected });
|
this.setState({ selected });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelect (row) {
|
handleSelect(row) {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
|
|
||||||
if (selected.some(s => s.id === row.id)) {
|
if (selected.some(s => s.id === row.id)) {
|
||||||
@@ -74,16 +70,16 @@ class OrganizationsList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteErrorClose () {
|
handleDeleteErrorClose() {
|
||||||
this.setState({ hasDeletionError: false });
|
this.setState({ hasDeletionError: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleOrgDelete () {
|
async handleOrgDelete() {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
|
|
||||||
this.setState({ hasContentLoading: true, hasDeletionError: false });
|
this.setState({ hasContentLoading: true, hasDeletionError: false });
|
||||||
try {
|
try {
|
||||||
await Promise.all(selected.map((org) => OrganizationsAPI.destroy(org.id)));
|
await Promise.all(selected.map(org => OrganizationsAPI.destroy(org.id)));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ hasDeletionError: true });
|
this.setState({ hasDeletionError: true });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -91,7 +87,7 @@ class OrganizationsList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadOrganizations () {
|
async loadOrganizations() {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
const { actions: cachedActions } = this.state;
|
const { actions: cachedActions } = this.state;
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
@@ -108,9 +104,16 @@ class OrganizationsList extends Component {
|
|||||||
optionsPromise,
|
optionsPromise,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.setState({ hasContentError: false, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
try {
|
try {
|
||||||
const [{ data: { count, results } }, { data: { actions } }] = await promises;
|
const [
|
||||||
|
{
|
||||||
|
data: { count, results },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: { actions },
|
||||||
|
},
|
||||||
|
] = await promises;
|
||||||
this.setState({
|
this.setState({
|
||||||
actions,
|
actions,
|
||||||
itemCount: count,
|
itemCount: count,
|
||||||
@@ -118,20 +121,18 @@ class OrganizationsList extends Component {
|
|||||||
selected: [],
|
selected: [],
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState(({ hasContentError: true }));
|
this.setState({ contentError: err });
|
||||||
} finally {
|
} finally {
|
||||||
this.setState({ hasContentLoading: false });
|
this.setState({ hasContentLoading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const { medium } = PageSectionVariants;
|
||||||
medium,
|
|
||||||
} = PageSectionVariants;
|
|
||||||
const {
|
const {
|
||||||
actions,
|
actions,
|
||||||
itemCount,
|
itemCount,
|
||||||
hasContentError,
|
contentError,
|
||||||
hasContentLoading,
|
hasContentLoading,
|
||||||
hasDeletionError,
|
hasDeletionError,
|
||||||
selected,
|
selected,
|
||||||
@@ -139,7 +140,8 @@ class OrganizationsList extends Component {
|
|||||||
} = this.state;
|
} = this.state;
|
||||||
const { match, i18n } = this.props;
|
const { match, i18n } = this.props;
|
||||||
|
|
||||||
const canAdd = actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
const canAdd =
|
||||||
|
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||||
const isAllSelected = selected.length === organizations.length;
|
const isAllSelected = selected.length === organizations.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -147,18 +149,33 @@ class OrganizationsList extends Component {
|
|||||||
<PageSection variant={medium}>
|
<PageSection variant={medium}>
|
||||||
<Card>
|
<Card>
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
hasContentError={hasContentError}
|
error={contentError}
|
||||||
hasContentLoading={hasContentLoading}
|
hasContentLoading={hasContentLoading}
|
||||||
items={organizations}
|
items={organizations}
|
||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
itemName="organization"
|
itemName="organization"
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
toolbarColumns={[
|
toolbarColumns={[
|
||||||
{ name: i18n._(t`Name`), key: 'name', isSortable: true, isSearchable: true },
|
{
|
||||||
{ name: i18n._(t`Modified`), key: 'modified', isSortable: true, isNumeric: true },
|
name: i18n._(t`Name`),
|
||||||
{ name: i18n._(t`Created`), key: 'created', isSortable: true, isNumeric: true },
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified`),
|
||||||
|
key: 'modified',
|
||||||
|
isSortable: true,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created`),
|
||||||
|
key: 'created',
|
||||||
|
isSortable: true,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
renderToolbar={(props) => (
|
renderToolbar={props => (
|
||||||
<DataListToolbar
|
<DataListToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
showSelectAll
|
||||||
@@ -171,13 +188,13 @@ class OrganizationsList extends Component {
|
|||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
itemName="Organization"
|
itemName="Organization"
|
||||||
/>,
|
/>,
|
||||||
canAdd
|
canAdd ? (
|
||||||
? <ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
|
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
|
||||||
: null,
|
) : null,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderItem={(o) => (
|
renderItem={o => (
|
||||||
<OrganizationListItem
|
<OrganizationListItem
|
||||||
key={o.id}
|
key={o.id}
|
||||||
organization={o}
|
organization={o}
|
||||||
@@ -187,8 +204,9 @@ class OrganizationsList extends Component {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
emptyStateControls={
|
emptyStateControls={
|
||||||
canAdd ? <ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
|
canAdd ? (
|
||||||
: null
|
<ToolbarAddButton key="add" linkTo={`${match.url}/add`} />
|
||||||
|
) : null
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const COLUMNS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
class OrganizationNotifications extends Component {
|
class OrganizationNotifications extends Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
contentError: null,
|
contentError: null,
|
||||||
@@ -38,22 +38,24 @@ class OrganizationNotifications extends Component {
|
|||||||
typeLabels: null,
|
typeLabels: null,
|
||||||
};
|
};
|
||||||
this.handleNotificationToggle = this.handleNotificationToggle.bind(this);
|
this.handleNotificationToggle = this.handleNotificationToggle.bind(this);
|
||||||
this.handleNotificationErrorClose = this.handleNotificationErrorClose.bind(this);
|
this.handleNotificationErrorClose = this.handleNotificationErrorClose.bind(
|
||||||
|
this
|
||||||
|
);
|
||||||
this.loadNotifications = this.loadNotifications.bind(this);
|
this.loadNotifications = this.loadNotifications.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.loadNotifications();
|
this.loadNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
if (location !== prevProps.location) {
|
if (location !== prevProps.location) {
|
||||||
this.loadNotifications();
|
this.loadNotifications();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadNotifications () {
|
async loadNotifications() {
|
||||||
const { id, location } = this.props;
|
const { id, location } = this.props;
|
||||||
const { typeLabels } = this.state;
|
const { typeLabels } = this.state;
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
@@ -67,13 +69,13 @@ class OrganizationNotifications extends Component {
|
|||||||
this.setState({ contentError: null, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: {
|
data: { count: itemCount = 0, results: notifications = [] },
|
||||||
count: itemCount = 0,
|
|
||||||
results: notifications = [],
|
|
||||||
}
|
|
||||||
} = await OrganizationsAPI.readNotificationTemplates(id, params);
|
} = await OrganizationsAPI.readNotificationTemplates(id, params);
|
||||||
|
|
||||||
const optionsResponse = await OrganizationsAPI.readOptionsNotificationTemplates(id, params);
|
const optionsResponse = await OrganizationsAPI.readOptionsNotificationTemplates(
|
||||||
|
id,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
let idMatchParams;
|
let idMatchParams;
|
||||||
if (notifications.length > 0) {
|
if (notifications.length > 0) {
|
||||||
@@ -122,7 +124,7 @@ class OrganizationNotifications extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleNotificationToggle (notificationId, isCurrentlyOn, status) {
|
async handleNotificationToggle(notificationId, isCurrentlyOn, status) {
|
||||||
const { id } = this.props;
|
const { id } = this.props;
|
||||||
|
|
||||||
let stateArrayName;
|
let stateArrayName;
|
||||||
@@ -135,13 +137,15 @@ class OrganizationNotifications extends Component {
|
|||||||
let stateUpdateFunction;
|
let stateUpdateFunction;
|
||||||
if (isCurrentlyOn) {
|
if (isCurrentlyOn) {
|
||||||
// when switching off, remove the toggled notification id from the array
|
// when switching off, remove the toggled notification id from the array
|
||||||
stateUpdateFunction = (prevState) => ({
|
stateUpdateFunction = prevState => ({
|
||||||
[stateArrayName]: prevState[stateArrayName].filter(i => i !== notificationId)
|
[stateArrayName]: prevState[stateArrayName].filter(
|
||||||
|
i => i !== notificationId
|
||||||
|
),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// when switching on, add the toggled notification id to the array
|
// when switching on, add the toggled notification id to the array
|
||||||
stateUpdateFunction = (prevState) => ({
|
stateUpdateFunction = prevState => ({
|
||||||
[stateArrayName]: prevState[stateArrayName].concat(notificationId)
|
[stateArrayName]: prevState[stateArrayName].concat(notificationId),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,11 +165,11 @@ class OrganizationNotifications extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNotificationErrorClose () {
|
handleNotificationErrorClose() {
|
||||||
this.setState({ toggleError: false });
|
this.setState({ toggleError: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const { canToggleNotifications, i18n } = this.props;
|
const { canToggleNotifications, i18n } = this.props;
|
||||||
const {
|
const {
|
||||||
contentError,
|
contentError,
|
||||||
@@ -176,7 +180,7 @@ class OrganizationNotifications extends Component {
|
|||||||
notifications,
|
notifications,
|
||||||
successTemplateIds,
|
successTemplateIds,
|
||||||
errorTemplateIds,
|
errorTemplateIds,
|
||||||
typeLabels
|
typeLabels,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -189,7 +193,7 @@ class OrganizationNotifications extends Component {
|
|||||||
itemName="notification"
|
itemName="notification"
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
toolbarColumns={COLUMNS}
|
toolbarColumns={COLUMNS}
|
||||||
renderItem={(notification) => (
|
renderItem={notification => (
|
||||||
<NotificationListItem
|
<NotificationListItem
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
notification={notification}
|
notification={notification}
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ exports[`<OrganizationNotifications /> initially renders succesfully 1`] = `
|
|||||||
<Route>
|
<Route>
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
contentError={null}
|
contentError={null}
|
||||||
hasContentError={false}
|
|
||||||
hasContentLoading={false}
|
hasContentLoading={false}
|
||||||
history={"/history/"}
|
history={"/history/"}
|
||||||
i18n={"/i18n/"}
|
i18n={"/i18n/"}
|
||||||
|
|||||||
@@ -8,30 +8,24 @@ import { QuestionCircleIcon } from '@patternfly/react-icons';
|
|||||||
import { InstanceGroupsAPI } from '@api';
|
import { InstanceGroupsAPI } from '@api';
|
||||||
import Lookup from '@components/Lookup';
|
import Lookup from '@components/Lookup';
|
||||||
|
|
||||||
const getInstanceGroups = async (params) => InstanceGroupsAPI.read(params);
|
const getInstanceGroups = async params => InstanceGroupsAPI.read(params);
|
||||||
|
|
||||||
class InstanceGroupsLookup extends React.Component {
|
class InstanceGroupsLookup extends React.Component {
|
||||||
render () {
|
render() {
|
||||||
const { value, tooltip, onChange, i18n } = this.props;
|
const { value, tooltip, onChange, i18n } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={(
|
label={
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{i18n._(t`Instance Groups`)}
|
{i18n._(t`Instance Groups`)}{' '}
|
||||||
{' '}
|
{tooltip && (
|
||||||
{
|
<Tooltip position="right" content={tooltip}>
|
||||||
tooltip && (
|
<QuestionCircleIcon />
|
||||||
<Tooltip
|
</Tooltip>
|
||||||
position="right"
|
)}
|
||||||
content={tooltip}
|
|
||||||
>
|
|
||||||
<QuestionCircleIcon />
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
}
|
||||||
fieldId="org-instance-groups"
|
fieldId="org-instance-groups"
|
||||||
>
|
>
|
||||||
<Lookup
|
<Lookup
|
||||||
@@ -43,9 +37,24 @@ class InstanceGroupsLookup extends React.Component {
|
|||||||
getItems={getInstanceGroups}
|
getItems={getInstanceGroups}
|
||||||
multiple
|
multiple
|
||||||
columns={[
|
columns={[
|
||||||
{ name: i18n._(t`Name`), key: 'name', isSortable: true, isSearchable: true },
|
{
|
||||||
{ name: i18n._(t`Modified`), key: 'modified', isSortable: false, isNumeric: true },
|
name: i18n._(t`Name`),
|
||||||
{ name: i18n._(t`Created`), key: 'created', isSortable: false, isNumeric: true }
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified`),
|
||||||
|
key: 'modified',
|
||||||
|
isSortable: false,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created`),
|
||||||
|
key: 'created',
|
||||||
|
isSortable: false,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
sortedColumnKey="name"
|
sortedColumnKey="name"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,21 +2,17 @@ import React, { Component } from 'react';
|
|||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import {
|
import { Card, PageSection, PageSectionVariants } from '@patternfly/react-core';
|
||||||
Card,
|
|
||||||
PageSection,
|
|
||||||
PageSectionVariants,
|
|
||||||
} from '@patternfly/react-core';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
JobTemplatesAPI,
|
JobTemplatesAPI,
|
||||||
UnifiedJobTemplatesAPI,
|
UnifiedJobTemplatesAPI,
|
||||||
WorkflowJobTemplatesAPI
|
WorkflowJobTemplatesAPI,
|
||||||
} from '@api';
|
} from '@api';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import DatalistToolbar from '@components/DataListToolbar';
|
import DatalistToolbar from '@components/DataListToolbar';
|
||||||
import PaginatedDataList, {
|
import PaginatedDataList, {
|
||||||
ToolbarDeleteButton
|
ToolbarDeleteButton,
|
||||||
} from '@components/PaginatedDataList';
|
} from '@components/PaginatedDataList';
|
||||||
import { getQSConfig, parseQueryString } from '@util/qs';
|
import { getQSConfig, parseQueryString } from '@util/qs';
|
||||||
|
|
||||||
@@ -28,16 +24,16 @@ const QS_CONFIG = getQSConfig('template', {
|
|||||||
page: 1,
|
page: 1,
|
||||||
page_size: 5,
|
page_size: 5,
|
||||||
order_by: 'name',
|
order_by: 'name',
|
||||||
type: 'job_template,workflow_job_template'
|
type: 'job_template,workflow_job_template',
|
||||||
});
|
});
|
||||||
|
|
||||||
class TemplatesList extends Component {
|
class TemplatesList extends Component {
|
||||||
constructor (props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
hasContentError: false,
|
contentError: null,
|
||||||
hasDeletionError: false,
|
hasDeletionError: false,
|
||||||
selected: [],
|
selected: [],
|
||||||
templates: [],
|
templates: [],
|
||||||
@@ -50,28 +46,28 @@ class TemplatesList extends Component {
|
|||||||
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount() {
|
||||||
this.loadTemplates();
|
this.loadTemplates();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
if (location !== prevProps.location) {
|
if (location !== prevProps.location) {
|
||||||
this.loadTemplates();
|
this.loadTemplates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDeleteErrorClose () {
|
handleDeleteErrorClose() {
|
||||||
this.setState({ hasDeletionError: false });
|
this.setState({ hasDeletionError: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelectAll (isSelected) {
|
handleSelectAll(isSelected) {
|
||||||
const { templates } = this.state;
|
const { templates } = this.state;
|
||||||
const selected = isSelected ? [...templates] : [];
|
const selected = isSelected ? [...templates] : [];
|
||||||
this.setState({ selected });
|
this.setState({ selected });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSelect (template) {
|
handleSelect(template) {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
if (selected.some(s => s.id === template.id)) {
|
if (selected.some(s => s.id === template.id)) {
|
||||||
this.setState({ selected: selected.filter(s => s.id !== template.id) });
|
this.setState({ selected: selected.filter(s => s.id !== template.id) });
|
||||||
@@ -80,20 +76,22 @@ class TemplatesList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleTemplateDelete () {
|
async handleTemplateDelete() {
|
||||||
const { selected } = this.state;
|
const { selected } = this.state;
|
||||||
|
|
||||||
this.setState({ hasContentLoading: true, hasDeletionError: false });
|
this.setState({ hasContentLoading: true, hasDeletionError: false });
|
||||||
try {
|
try {
|
||||||
await Promise.all(selected.map(({ type, id }) => {
|
await Promise.all(
|
||||||
let deletePromise;
|
selected.map(({ type, id }) => {
|
||||||
if (type === 'job_template') {
|
let deletePromise;
|
||||||
deletePromise = JobTemplatesAPI.destroy(id);
|
if (type === 'job_template') {
|
||||||
} else if (type === 'workflow_job_template') {
|
deletePromise = JobTemplatesAPI.destroy(id);
|
||||||
deletePromise = WorkflowJobTemplatesAPI.destroy(id);
|
} else if (type === 'workflow_job_template') {
|
||||||
}
|
deletePromise = WorkflowJobTemplatesAPI.destroy(id);
|
||||||
return deletePromise;
|
}
|
||||||
}));
|
return deletePromise;
|
||||||
|
})
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ hasDeletionError: true });
|
this.setState({ hasDeletionError: true });
|
||||||
} finally {
|
} finally {
|
||||||
@@ -101,56 +99,70 @@ class TemplatesList extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadTemplates () {
|
async loadTemplates() {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
|
||||||
this.setState({ hasContentError: false, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
try {
|
try {
|
||||||
const { data: { count, results } } = await UnifiedJobTemplatesAPI.read(params);
|
const {
|
||||||
|
data: { count, results },
|
||||||
|
} = await UnifiedJobTemplatesAPI.read(params);
|
||||||
this.setState({
|
this.setState({
|
||||||
itemCount: count,
|
itemCount: count,
|
||||||
templates: results,
|
templates: results,
|
||||||
selected: [],
|
selected: [],
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ hasContentError: true });
|
this.setState({ contentError: err });
|
||||||
} finally {
|
} finally {
|
||||||
this.setState({ hasContentLoading: false });
|
this.setState({ hasContentLoading: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
hasContentError,
|
contentError,
|
||||||
hasContentLoading,
|
hasContentLoading,
|
||||||
hasDeletionError,
|
hasDeletionError,
|
||||||
templates,
|
templates,
|
||||||
itemCount,
|
itemCount,
|
||||||
selected,
|
selected,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const { match, i18n } = this.props;
|
||||||
match,
|
|
||||||
i18n
|
|
||||||
} = this.props;
|
|
||||||
const isAllSelected = selected.length === templates.length;
|
const isAllSelected = selected.length === templates.length;
|
||||||
const { medium } = PageSectionVariants;
|
const { medium } = PageSectionVariants;
|
||||||
return (
|
return (
|
||||||
<PageSection variant={medium}>
|
<PageSection variant={medium}>
|
||||||
<Card>
|
<Card>
|
||||||
<PaginatedDataList
|
<PaginatedDataList
|
||||||
hasContentError={hasContentError}
|
err={contentError}
|
||||||
hasContentLoading={hasContentLoading}
|
hasContentLoading={hasContentLoading}
|
||||||
items={templates}
|
items={templates}
|
||||||
itemCount={itemCount}
|
itemCount={itemCount}
|
||||||
itemName={i18n._(t`Template`)}
|
itemName={i18n._(t`Template`)}
|
||||||
qsConfig={QS_CONFIG}
|
qsConfig={QS_CONFIG}
|
||||||
toolbarColumns={[
|
toolbarColumns={[
|
||||||
{ name: i18n._(t`Name`), key: 'name', isSortable: true, isSearchable: true },
|
{
|
||||||
{ name: i18n._(t`Modified`), key: 'modified', isSortable: true, isNumeric: true },
|
name: i18n._(t`Name`),
|
||||||
{ name: i18n._(t`Created`), key: 'created', isSortable: true, isNumeric: true },
|
key: 'name',
|
||||||
|
isSortable: true,
|
||||||
|
isSearchable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Modified`),
|
||||||
|
key: 'modified',
|
||||||
|
isSortable: true,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: i18n._(t`Created`),
|
||||||
|
key: 'created',
|
||||||
|
isSortable: true,
|
||||||
|
isNumeric: true,
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
renderToolbar={(props) => (
|
renderToolbar={props => (
|
||||||
<DatalistToolbar
|
<DatalistToolbar
|
||||||
{...props}
|
{...props}
|
||||||
showSelectAll
|
showSelectAll
|
||||||
@@ -163,11 +175,11 @@ class TemplatesList extends Component {
|
|||||||
onDelete={this.handleTemplateDelete}
|
onDelete={this.handleTemplateDelete}
|
||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
itemName={i18n._(t`Template`)}
|
itemName={i18n._(t`Template`)}
|
||||||
/>
|
/>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderItem={(template) => (
|
renderItem={template => (
|
||||||
<TemplateListItem
|
<TemplateListItem
|
||||||
key={template.id}
|
key={template.id}
|
||||||
value={template.name}
|
value={template.name}
|
||||||
|
|||||||
@@ -4,16 +4,17 @@
|
|||||||
* @param {array} array in the format [ [ key, value ], ...]
|
* @param {array} array in the format [ [ key, value ], ...]
|
||||||
* @return {object} object in the forms { key: value, ... }
|
* @return {object} object in the forms { key: value, ... }
|
||||||
*/
|
*/
|
||||||
const toObject = (entriesArr) => entriesArr.reduce((acc, [key, value]) => {
|
const toObject = entriesArr =>
|
||||||
if (acc[key] && Array.isArray(acc[key])) {
|
entriesArr.reduce((acc, [key, value]) => {
|
||||||
acc[key].push(value);
|
if (acc[key] && Array.isArray(acc[key])) {
|
||||||
} else if (acc[key]) {
|
acc[key].push(value);
|
||||||
acc[key] = [acc[key], value];
|
} else if (acc[key]) {
|
||||||
} else {
|
acc[key] = [acc[key], value];
|
||||||
acc[key] = value;
|
} else {
|
||||||
}
|
acc[key] = value;
|
||||||
return acc;
|
}
|
||||||
}, {});
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to namespace params object
|
* helper function to namespace params object
|
||||||
@@ -30,7 +31,7 @@ const namespaceParams = (namespace, params = {}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return namespaced || {};
|
return namespaced || {};
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to remove namespace from params object
|
* helper function to remove namespace from params object
|
||||||
@@ -47,7 +48,7 @@ const denamespaceParams = (namespace, params = {}) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return denamespaced || {};
|
return denamespaced || {};
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to check the namespace of a param is what you expec
|
* helper function to check the namespace of a param is what you expec
|
||||||
@@ -59,7 +60,7 @@ const namespaceMatches = (namespace, fieldname) => {
|
|||||||
if (!namespace) return !fieldname.includes('.');
|
if (!namespace) return !fieldname.includes('.');
|
||||||
|
|
||||||
return fieldname.startsWith(`${namespace}.`);
|
return fieldname.startsWith(`${namespace}.`);
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to check the value of a param is equal to another
|
* helper function to check the value of a param is equal to another
|
||||||
@@ -72,13 +73,15 @@ const paramValueIsEqual = (one, two) => {
|
|||||||
|
|
||||||
if (Array.isArray(one) && Array.isArray(two)) {
|
if (Array.isArray(one) && Array.isArray(two)) {
|
||||||
isEqual = one.filter(val => two.indexOf(val) > -1).length === 0;
|
isEqual = one.filter(val => two.indexOf(val) > -1).length === 0;
|
||||||
} else if ((typeof(one) === "string" && typeof(two) === "string") ||
|
} else if (
|
||||||
(typeof(one) === "number" && typeof(two) === "number")){
|
(typeof one === 'string' && typeof two === 'string') ||
|
||||||
|
(typeof one === 'number' && typeof two === 'number')
|
||||||
|
) {
|
||||||
isEqual = one === two;
|
isEqual = one === two;
|
||||||
}
|
}
|
||||||
|
|
||||||
return isEqual;
|
return isEqual;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert query param object to url query string
|
* Convert query param object to url query string
|
||||||
@@ -87,17 +90,19 @@ const paramValueIsEqual = (one, two) => {
|
|||||||
* @param {object} query param object
|
* @param {object} query param object
|
||||||
* @return {string} url query string
|
* @return {string} url query string
|
||||||
*/
|
*/
|
||||||
export const encodeQueryString = (params) => {
|
export const encodeQueryString = params => {
|
||||||
if (!params) return '';
|
if (!params) return '';
|
||||||
|
|
||||||
return Object.keys(params)
|
return Object.keys(params)
|
||||||
.sort()
|
.sort()
|
||||||
.filter(key => params[key] !== null)
|
.filter(key => params[key] !== null)
|
||||||
.map(key => ([key, params[key]]))
|
.map(key => [key, params[key]])
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
// if value is array, should return more than one key value pair
|
// if value is array, should return more than one key value pair
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.map(val => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
|
return value
|
||||||
|
.map(val => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`)
|
||||||
|
.join('&');
|
||||||
}
|
}
|
||||||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||||
})
|
})
|
||||||
@@ -115,22 +120,31 @@ export const encodeNonDefaultQueryString = (config, params) => {
|
|||||||
if (!params) return '';
|
if (!params) return '';
|
||||||
|
|
||||||
const namespacedParams = namespaceParams(config.namespace, params);
|
const namespacedParams = namespaceParams(config.namespace, params);
|
||||||
const namespacedDefaults = namespaceParams(config.namespace, config.defaultParams);
|
const namespacedDefaults = namespaceParams(
|
||||||
|
config.namespace,
|
||||||
|
config.defaultParams
|
||||||
|
);
|
||||||
const namespacedDefaultKeys = Object.keys(namespacedDefaults);
|
const namespacedDefaultKeys = Object.keys(namespacedDefaults);
|
||||||
const namespacedParamsWithoutDefaultsKeys = Object.keys(namespacedParams)
|
const namespacedParamsWithoutDefaultsKeys = Object.keys(
|
||||||
.filter(key => namespacedDefaultKeys.indexOf(key) === -1 ||
|
namespacedParams
|
||||||
!paramValueIsEqual(namespacedParams[key], namespacedDefaults[key]));
|
).filter(
|
||||||
|
key =>
|
||||||
|
namespacedDefaultKeys.indexOf(key) === -1 ||
|
||||||
|
!paramValueIsEqual(namespacedParams[key], namespacedDefaults[key])
|
||||||
|
);
|
||||||
|
|
||||||
return namespacedParamsWithoutDefaultsKeys
|
return namespacedParamsWithoutDefaultsKeys
|
||||||
.sort()
|
.sort()
|
||||||
.filter(key => namespacedParams[key] !== null)
|
.filter(key => namespacedParams[key] !== null)
|
||||||
.map(key => {
|
.map(key => {
|
||||||
return ([key, namespacedParams[key]])
|
return [key, namespacedParams[key]];
|
||||||
})
|
})
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
// if value is array, should return more than one key value pair
|
// if value is array, should return more than one key value pair
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
return value.map(val => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
|
return value
|
||||||
|
.map(val => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`)
|
||||||
|
.join('&');
|
||||||
}
|
}
|
||||||
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
||||||
})
|
})
|
||||||
@@ -144,7 +158,7 @@ export const encodeNonDefaultQueryString = (config, params) => {
|
|||||||
* @param {array} params that are number fields
|
* @param {array} params that are number fields
|
||||||
* @return {object} query param object
|
* @return {object} query param object
|
||||||
*/
|
*/
|
||||||
export function getQSConfig (
|
export function getQSConfig(
|
||||||
namespace,
|
namespace,
|
||||||
defaultParams = { page: 1, page_size: 5, order_by: 'name' },
|
defaultParams = { page: 1, page_size: 5, order_by: 'name' },
|
||||||
integerFields = ['page', 'page_size']
|
integerFields = ['page', 'page_size']
|
||||||
@@ -165,12 +179,16 @@ export function getQSConfig (
|
|||||||
* @param {string} url query string
|
* @param {string} url query string
|
||||||
* @return {object} query param object
|
* @return {object} query param object
|
||||||
*/
|
*/
|
||||||
export function parseQueryString (config, queryString) {
|
export function parseQueryString(config, queryString) {
|
||||||
if (!queryString) return config.defaultParams;
|
if (!queryString) return config.defaultParams;
|
||||||
|
|
||||||
const namespacedIntegerFields = config.integerFields.map(f => config.namespace ? `${config.namespace}.${f}` : f);
|
const namespacedIntegerFields = config.integerFields.map(f =>
|
||||||
|
config.namespace ? `${config.namespace}.${f}` : f
|
||||||
|
);
|
||||||
|
|
||||||
const keyValuePairs = queryString.replace(/^\?/, '').split('&')
|
const keyValuePairs = queryString
|
||||||
|
.replace(/^\?/, '')
|
||||||
|
.split('&')
|
||||||
.map(s => s.split('='))
|
.map(s => s.split('='))
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
if (namespacedIntegerFields.includes(key)) {
|
if (namespacedIntegerFields.includes(key)) {
|
||||||
@@ -185,37 +203,38 @@ export function parseQueryString (config, queryString) {
|
|||||||
// needs to return array for duplicate keys
|
// needs to return array for duplicate keys
|
||||||
// ie [[k1, v1], [k1, v2], [k2, v3]]
|
// ie [[k1, v1], [k1, v2], [k2, v3]]
|
||||||
// -> [[k1, [v1, v2]], [k2, v3]]
|
// -> [[k1, [v1, v2]], [k2, v3]]
|
||||||
const dedupedKeyValuePairs = Object.keys(keyValueObject)
|
const dedupedKeyValuePairs = Object.keys(keyValueObject).map(key => {
|
||||||
.map(key => {
|
const values = keyValuePairs.filter(([k]) => k === key).map(([, v]) => v);
|
||||||
const values = keyValuePairs
|
|
||||||
.filter(([k]) => k === key)
|
|
||||||
.map(([, v]) => v);
|
|
||||||
|
|
||||||
if (values.length === 1) {
|
if (values.length === 1) {
|
||||||
return [key, values[0]];
|
return [key, values[0]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [key, values];
|
return [key, values];
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsed = Object.assign(...dedupedKeyValuePairs.map(([k, v]) => ({
|
const parsed = Object.assign(
|
||||||
[k]: v
|
...dedupedKeyValuePairs.map(([k, v]) => ({
|
||||||
})));
|
[k]: v,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
const namespacedParams = {};
|
const namespacedParams = {};
|
||||||
|
|
||||||
Object.keys(parsed)
|
Object.keys(parsed).forEach(field => {
|
||||||
.forEach(field => {
|
if (namespaceMatches(config.namespace, field)) {
|
||||||
if (namespaceMatches(config.namespace, field)) {
|
let fieldname = field;
|
||||||
let fieldname = field;
|
if (config.namespace) {
|
||||||
if (config.namespace) {
|
fieldname = field.substr(config.namespace.length + 1);
|
||||||
fieldname = field.substr(config.namespace.length + 1);
|
|
||||||
}
|
|
||||||
namespacedParams[fieldname] = parsed[field];
|
|
||||||
}
|
}
|
||||||
});
|
namespacedParams[fieldname] = parsed[field];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const namespacedDefaults = namespaceParams(config.namespace, config.defaultParams);
|
const namespacedDefaults = namespaceParams(
|
||||||
|
config.namespace,
|
||||||
|
config.defaultParams
|
||||||
|
);
|
||||||
|
|
||||||
Object.keys(namespacedDefaults)
|
Object.keys(namespacedDefaults)
|
||||||
.filter(key => Object.keys(parsed).indexOf(key) === -1)
|
.filter(key => Object.keys(parsed).indexOf(key) === -1)
|
||||||
@@ -238,11 +257,12 @@ export function parseQueryString (config, queryString) {
|
|||||||
* @param {object} namespaced params object of default params
|
* @param {object} namespaced params object of default params
|
||||||
* @return {object} namespaced params object of only defaults
|
* @return {object} namespaced params object of only defaults
|
||||||
*/
|
*/
|
||||||
const getDefaultParams = (params, defaults) => toObject(
|
const getDefaultParams = (params, defaults) =>
|
||||||
Object.keys(params)
|
toObject(
|
||||||
.filter(key => Object.keys(defaults).indexOf(key) > -1)
|
Object.keys(params)
|
||||||
.map(key => [key, params[key]])
|
.filter(key => Object.keys(defaults).indexOf(key) > -1)
|
||||||
);
|
.map(key => [key, params[key]])
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to get params that are not defaults
|
* helper function to get params that are not defaults
|
||||||
@@ -250,11 +270,12 @@ const getDefaultParams = (params, defaults) => toObject(
|
|||||||
* @param {object} namespaced params object of default params
|
* @param {object} namespaced params object of default params
|
||||||
* @return {object} namespaced params object of non-defaults
|
* @return {object} namespaced params object of non-defaults
|
||||||
*/
|
*/
|
||||||
const getNonDefaultParams = (params, defaults) => toObject(
|
const getNonDefaultParams = (params, defaults) =>
|
||||||
Object.keys(params)
|
toObject(
|
||||||
.filter(key => Object.keys(defaults).indexOf(key) === -1)
|
Object.keys(params)
|
||||||
.map(key => [key, params[key]])
|
.filter(key => Object.keys(defaults).indexOf(key) === -1)
|
||||||
);
|
.map(key => [key, params[key]])
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to merge old and new params together
|
* helper function to merge old and new params together
|
||||||
@@ -262,9 +283,9 @@ const getNonDefaultParams = (params, defaults) => toObject(
|
|||||||
* @param {object} namespaced params object of new params
|
* @param {object} namespaced params object of new params
|
||||||
* @return {object} merged namespaced params object
|
* @return {object} merged namespaced params object
|
||||||
*/
|
*/
|
||||||
const getMergedParams = (oldParams, newParams) => toObject(
|
const getMergedParams = (oldParams, newParams) =>
|
||||||
Object.keys(oldParams)
|
toObject(
|
||||||
.map(key => {
|
Object.keys(oldParams).map(key => {
|
||||||
let oldVal = oldParams[key];
|
let oldVal = oldParams[key];
|
||||||
const newVal = newParams[key];
|
const newVal = newParams[key];
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
@@ -276,7 +297,7 @@ const getMergedParams = (oldParams, newParams) => toObject(
|
|||||||
}
|
}
|
||||||
return [key, oldVal];
|
return [key, oldVal];
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* helper function to get new params that are not in merged params
|
* helper function to get new params that are not in merged params
|
||||||
@@ -284,11 +305,12 @@ const getMergedParams = (oldParams, newParams) => toObject(
|
|||||||
* @param {object} namespaced params object of new params
|
* @param {object} namespaced params object of new params
|
||||||
* @return {object} remaining new namespaced params object
|
* @return {object} remaining new namespaced params object
|
||||||
*/
|
*/
|
||||||
const getRemainingNewParams = (mergedParams, newParams) => toObject(
|
const getRemainingNewParams = (mergedParams, newParams) =>
|
||||||
Object.keys(newParams)
|
toObject(
|
||||||
.filter(key => Object.keys(mergedParams).indexOf(key) === -1)
|
Object.keys(newParams)
|
||||||
.map(key => [key, newParams[key]])
|
.filter(key => Object.keys(mergedParams).indexOf(key) === -1)
|
||||||
);
|
.map(key => [key, newParams[key]])
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Merges existing params of search string with new ones and returns the updated list of params
|
* Merges existing params of search string with new ones and returns the updated list of params
|
||||||
@@ -297,13 +319,16 @@ const getRemainingNewParams = (mergedParams, newParams) => toObject(
|
|||||||
* @param {object} object with new params to add
|
* @param {object} object with new params to add
|
||||||
* @return {object} query param object
|
* @return {object} query param object
|
||||||
*/
|
*/
|
||||||
export function addParams (config, searchString, newParams) {
|
export function addParams(config, searchString, newParams) {
|
||||||
const namespacedOldParams = namespaceParams(
|
const namespacedOldParams = namespaceParams(
|
||||||
config.namespace,
|
config.namespace,
|
||||||
parseQueryString(config, searchString)
|
parseQueryString(config, searchString)
|
||||||
);
|
);
|
||||||
const namespacedNewParams = namespaceParams(config.namespace, newParams);
|
const namespacedNewParams = namespaceParams(config.namespace, newParams);
|
||||||
const namespacedDefaultParams = namespaceParams(config.namespace, config.defaultParams);
|
const namespacedDefaultParams = namespaceParams(
|
||||||
|
config.namespace,
|
||||||
|
config.defaultParams
|
||||||
|
);
|
||||||
|
|
||||||
const namespacedOldParamsNotDefaults = getNonDefaultParams(
|
const namespacedOldParamsNotDefaults = getNonDefaultParams(
|
||||||
namespacedOldParams,
|
namespacedOldParams,
|
||||||
@@ -320,7 +345,7 @@ export function addParams (config, searchString, newParams) {
|
|||||||
return denamespaceParams(config.namespace, {
|
return denamespaceParams(config.namespace, {
|
||||||
...getDefaultParams(namespacedOldParams, namespacedDefaultParams),
|
...getDefaultParams(namespacedOldParams, namespacedDefaultParams),
|
||||||
...namespacedMergedParams,
|
...namespacedMergedParams,
|
||||||
...getRemainingNewParams(namespacedMergedParams, namespacedNewParams)
|
...getRemainingNewParams(namespacedMergedParams, namespacedNewParams),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,26 +356,29 @@ export function addParams (config, searchString, newParams) {
|
|||||||
* @param {object} object with new params to remove
|
* @param {object} object with new params to remove
|
||||||
* @return {object} query param object
|
* @return {object} query param object
|
||||||
*/
|
*/
|
||||||
export function removeParams (config, searchString, paramsToRemove) {
|
export function removeParams(config, searchString, paramsToRemove) {
|
||||||
const oldParams = parseQueryString(config, searchString);
|
const oldParams = parseQueryString(config, searchString);
|
||||||
const paramsEntries = [];
|
const paramsEntries = [];
|
||||||
Object.entries(oldParams)
|
Object.entries(oldParams).forEach(([key, value]) => {
|
||||||
.forEach(([key, value]) => {
|
if (Array.isArray(value)) {
|
||||||
if (Array.isArray(value)) {
|
value.forEach(val => {
|
||||||
value.forEach(val => {
|
paramsEntries.push([key, val]);
|
||||||
paramsEntries.push([key, val]);
|
});
|
||||||
})
|
} else {
|
||||||
} else {
|
paramsEntries.push([key, value]);
|
||||||
paramsEntries.push([key, value]);
|
}
|
||||||
}
|
});
|
||||||
})
|
|
||||||
const paramsToRemoveEntries = Object.entries(paramsToRemove);
|
const paramsToRemoveEntries = Object.entries(paramsToRemove);
|
||||||
const remainingEntries = paramsEntries
|
const remainingEntries = paramsEntries.filter(
|
||||||
.filter(([key, value]) => paramsToRemoveEntries
|
([key, value]) =>
|
||||||
.filter(([newKey, newValue]) => key === newKey && value === newValue).length === 0);
|
paramsToRemoveEntries.filter(
|
||||||
|
([newKey, newValue]) => key === newKey && value === newValue
|
||||||
|
).length === 0
|
||||||
|
);
|
||||||
const remainingObject = toObject(remainingEntries);
|
const remainingObject = toObject(remainingEntries);
|
||||||
const defaultEntriesLeftover = Object.entries(config.defaultParams)
|
const defaultEntriesLeftover = Object.entries(config.defaultParams).filter(
|
||||||
.filter(([key]) => !remainingObject[key]);
|
([key]) => !remainingObject[key]
|
||||||
|
);
|
||||||
const finalParamsEntries = remainingEntries;
|
const finalParamsEntries = remainingEntries;
|
||||||
defaultEntriesLeftover.forEach(value => {
|
defaultEntriesLeftover.forEach(value => {
|
||||||
finalParamsEntries.push(value);
|
finalParamsEntries.push(value);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
parseQueryString,
|
parseQueryString,
|
||||||
getQSConfig,
|
getQSConfig,
|
||||||
addParams,
|
addParams,
|
||||||
removeParams
|
removeParams,
|
||||||
} from './qs';
|
} from './qs';
|
||||||
|
|
||||||
describe('qs (qs.js)', () => {
|
describe('qs (qs.js)', () => {
|
||||||
@@ -13,14 +13,19 @@ describe('qs (qs.js)', () => {
|
|||||||
[
|
[
|
||||||
[null, ''],
|
[null, ''],
|
||||||
[{}, ''],
|
[{}, ''],
|
||||||
[{ order_by: 'name', page: 1, page_size: 5 }, 'order_by=name&page=1&page_size=5'],
|
[
|
||||||
[{ '-order_by': 'name', page: '1', page_size: 5 }, '-order_by=name&page=1&page_size=5'],
|
{ order_by: 'name', page: 1, page_size: 5 },
|
||||||
]
|
'order_by=name&page=1&page_size=5',
|
||||||
.forEach(([params, expectedQueryString]) => {
|
],
|
||||||
const actualQueryString = encodeQueryString(params);
|
[
|
||||||
|
{ '-order_by': 'name', page: '1', page_size: 5 },
|
||||||
|
'-order_by=name&page=1&page_size=5',
|
||||||
|
],
|
||||||
|
].forEach(([params, expectedQueryString]) => {
|
||||||
|
const actualQueryString = encodeQueryString(params);
|
||||||
|
|
||||||
expect(actualQueryString).toEqual(expectedQueryString);
|
expect(actualQueryString).toEqual(expectedQueryString);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('encodeQueryString omits null values', () => {
|
test('encodeQueryString omits null values', () => {
|
||||||
@@ -35,7 +40,7 @@ describe('qs (qs.js)', () => {
|
|||||||
describe('encodeNonDefaultQueryString', () => {
|
describe('encodeNonDefaultQueryString', () => {
|
||||||
const config = {
|
const config = {
|
||||||
namespace: null,
|
namespace: null,
|
||||||
defaultParams: { page: 1, page_size: 5, order_by: 'name'},
|
defaultParams: { page: 1, page_size: 5, order_by: 'name' },
|
||||||
integerFields: ['page'],
|
integerFields: ['page'],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,14 +50,19 @@ describe('qs (qs.js)', () => {
|
|||||||
[{}, ''],
|
[{}, ''],
|
||||||
[{ order_by: 'name', page: 1, page_size: 5 }, ''],
|
[{ order_by: 'name', page: 1, page_size: 5 }, ''],
|
||||||
[{ order_by: '-name', page: 1, page_size: 5 }, 'order_by=-name'],
|
[{ order_by: '-name', page: 1, page_size: 5 }, 'order_by=-name'],
|
||||||
[{ order_by: '-name', page: 3, page_size: 10 }, 'order_by=-name&page=3&page_size=10'],
|
[
|
||||||
[{ order_by: '-name', page: 3, page_size: 10, foo: 'bar' }, 'foo=bar&order_by=-name&page=3&page_size=10'],
|
{ order_by: '-name', page: 3, page_size: 10 },
|
||||||
]
|
'order_by=-name&page=3&page_size=10',
|
||||||
.forEach(([params, expectedQueryString]) => {
|
],
|
||||||
const actualQueryString = encodeNonDefaultQueryString(config, params);
|
[
|
||||||
|
{ order_by: '-name', page: 3, page_size: 10, foo: 'bar' },
|
||||||
|
'foo=bar&order_by=-name&page=3&page_size=10',
|
||||||
|
],
|
||||||
|
].forEach(([params, expectedQueryString]) => {
|
||||||
|
const actualQueryString = encodeNonDefaultQueryString(config, params);
|
||||||
|
|
||||||
expect(actualQueryString).toEqual(expectedQueryString);
|
expect(actualQueryString).toEqual(expectedQueryString);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('encodeNonDefaultQueryString omits null values', () => {
|
test('encodeNonDefaultQueryString omits null values', () => {
|
||||||
@@ -114,7 +124,7 @@ describe('qs (qs.js)', () => {
|
|||||||
const query = '';
|
const query = '';
|
||||||
expect(parseQueryString(config, query)).toEqual({
|
expect(parseQueryString(config, query)).toEqual({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 15
|
page_size: 15,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -222,7 +232,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&page=3';
|
const query = '?baz=bar&page=3';
|
||||||
const newParams = { bag: 'boom' }
|
const newParams = { bag: 'boom' };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: 'bar',
|
baz: 'bar',
|
||||||
bag: 'boom',
|
bag: 'boom',
|
||||||
@@ -238,7 +248,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&baz=bang&page=3';
|
const query = '?baz=bar&baz=bang&page=3';
|
||||||
const newParams = { baz: 'boom' }
|
const newParams = { baz: 'boom' };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang', 'boom'],
|
baz: ['bar', 'bang', 'boom'],
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -253,7 +263,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&baz=bang&page=3';
|
const query = '?baz=bar&baz=bang&page=3';
|
||||||
const newParams = { page: 5 }
|
const newParams = { page: 5 };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 5,
|
page: 5,
|
||||||
@@ -268,7 +278,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&baz=bang&page=3';
|
const query = '?baz=bar&baz=bang&page=3';
|
||||||
const newParams = { baz: 'bust', pat: 'pal' }
|
const newParams = { baz: 'bust', pat: 'pal' };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang', 'bust'],
|
baz: ['bar', 'bang', 'bust'],
|
||||||
pat: 'pal',
|
pat: 'pal',
|
||||||
@@ -284,7 +294,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.page=3';
|
const query = '?item.baz=bar&item.page=3';
|
||||||
const newParams = { bag: 'boom' }
|
const newParams = { bag: 'boom' };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: 'bar',
|
baz: 'bar',
|
||||||
bag: 'boom',
|
bag: 'boom',
|
||||||
@@ -300,7 +310,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&foo.page=3';
|
const query = '?item.baz=bar&foo.page=3';
|
||||||
const newParams = { bag: 'boom' }
|
const newParams = { bag: 'boom' };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: 'bar',
|
baz: 'bar',
|
||||||
bag: 'boom',
|
bag: 'boom',
|
||||||
@@ -316,7 +326,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
||||||
const newParams = { baz: 'boom' }
|
const newParams = { baz: 'boom' };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang', 'boom'],
|
baz: ['bar', 'bang', 'boom'],
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -331,7 +341,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
||||||
const newParams = { page: 5 }
|
const newParams = { page: 5 };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 5,
|
page: 5,
|
||||||
@@ -346,7 +356,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
||||||
const newParams = { baz: 'bust', pat: 'pal' }
|
const newParams = { baz: 'bust', pat: 'pal' };
|
||||||
expect(addParams(config, query, newParams)).toEqual({
|
expect(addParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang', 'bust'],
|
baz: ['bar', 'bang', 'bust'],
|
||||||
pat: 'pal',
|
pat: 'pal',
|
||||||
@@ -364,7 +374,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&page=3&bag=boom';
|
const query = '?baz=bar&page=3&bag=boom';
|
||||||
const newParams = { bag: 'boom' }
|
const newParams = { bag: 'boom' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: 'bar',
|
baz: 'bar',
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -379,7 +389,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&baz=bang&page=3';
|
const query = '?baz=bar&baz=bang&page=3';
|
||||||
const newParams = { baz: 'bar' }
|
const newParams = { baz: 'bar' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: 'bang',
|
baz: 'bang',
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -394,7 +404,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&baz=bang&baz=bust&page=3';
|
const query = '?baz=bar&baz=bang&baz=bust&page=3';
|
||||||
const newParams = { baz: 'bar' }
|
const newParams = { baz: 'bar' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bang', 'bust'],
|
baz: ['bang', 'bust'],
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -409,7 +419,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&baz=bang&page=3';
|
const query = '?baz=bar&baz=bang&page=3';
|
||||||
const newParams = { page: 3 }
|
const newParams = { page: 3 };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -424,7 +434,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?baz=bar&baz=bang&baz=bust&pat=pal&page=3';
|
const query = '?baz=bar&baz=bang&baz=bust&pat=pal&page=3';
|
||||||
const newParams = { baz: 'bust', pat: 'pal' }
|
const newParams = { baz: 'bust', pat: 'pal' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -439,7 +449,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.page=3';
|
const query = '?item.baz=bar&item.page=3';
|
||||||
const newParams = { baz: 'bar' }
|
const newParams = { baz: 'bar' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
page: 3,
|
page: 3,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
@@ -453,7 +463,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&foo.page=3';
|
const query = '?item.baz=bar&foo.page=3';
|
||||||
const newParams = { baz: 'bar' }
|
const newParams = { baz: 'bar' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
page: 1,
|
page: 1,
|
||||||
page_size: 15,
|
page_size: 15,
|
||||||
@@ -467,7 +477,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
||||||
const newParams = { baz: 'bar' }
|
const newParams = { baz: 'bar' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: 'bang',
|
baz: 'bang',
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -482,7 +492,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.baz=bang&item.baz=bust&item.page=3';
|
const query = '?item.baz=bar&item.baz=bang&item.baz=bust&item.page=3';
|
||||||
const newParams = { baz: 'bar' }
|
const newParams = { baz: 'bar' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bang', 'bust'],
|
baz: ['bang', 'bust'],
|
||||||
page: 3,
|
page: 3,
|
||||||
@@ -497,7 +507,7 @@ describe('qs (qs.js)', () => {
|
|||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
const query = '?item.baz=bar&item.baz=bang&item.page=3';
|
||||||
const newParams = { page: 3 }
|
const newParams = { page: 3 };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 1,
|
page: 1,
|
||||||
@@ -511,8 +521,9 @@ describe('qs (qs.js)', () => {
|
|||||||
defaultParams: { page: 1, page_size: 15 },
|
defaultParams: { page: 1, page_size: 15 },
|
||||||
integerFields: ['page', 'page_size'],
|
integerFields: ['page', 'page_size'],
|
||||||
};
|
};
|
||||||
const query = '?item.baz=bar&item.baz=bang&item.baz=bust&item.pat=pal&item.page=3';
|
const query =
|
||||||
const newParams = { baz: 'bust', pat: 'pal' }
|
'?item.baz=bar&item.baz=bang&item.baz=bust&item.pat=pal&item.page=3';
|
||||||
|
const newParams = { baz: 'bust', pat: 'pal' };
|
||||||
expect(removeParams(config, query, newParams)).toEqual({
|
expect(removeParams(config, query, newParams)).toEqual({
|
||||||
baz: ['bar', 'bang'],
|
baz: ['bar', 'bang'],
|
||||||
page: 3,
|
page: 3,
|
||||||
|
|||||||
Reference in New Issue
Block a user