mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 17:05:18 +00:00
Add delete button to custom Host Labels (#1014)
This commit is contained in:
parent
2b55cf3acf
commit
32c51d2be7
@ -43,6 +43,15 @@ class Kolide extends Base {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
destroy: (label) => {
|
||||||
|
const { LABELS } = endpoints;
|
||||||
|
const endpoint = this.endpoint(`${LABELS}/${label.id}`);
|
||||||
|
|
||||||
|
return this.authenticatedDelete(endpoint);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
createLabel = ({ description, name, query }) => {
|
createLabel = ({ description, name, query }) => {
|
||||||
const { LABELS } = endpoints;
|
const { LABELS } = endpoints;
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import nock from 'nock';
|
|||||||
import Kolide from 'kolide';
|
import Kolide from 'kolide';
|
||||||
import helpers from 'kolide/helpers';
|
import helpers from 'kolide/helpers';
|
||||||
import mocks from 'test/mocks';
|
import mocks from 'test/mocks';
|
||||||
import { configOptionStub, hostStub, packStub, queryStub, userStub } from 'test/stubs';
|
import { configOptionStub, hostStub, packStub, queryStub, userStub, labelStub } from 'test/stubs';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
invalidForgotPasswordRequest,
|
invalidForgotPasswordRequest,
|
||||||
@ -13,6 +13,7 @@ const {
|
|||||||
validCreatePackRequest,
|
validCreatePackRequest,
|
||||||
validCreateQueryRequest,
|
validCreateQueryRequest,
|
||||||
validCreateScheduledQueryRequest,
|
validCreateScheduledQueryRequest,
|
||||||
|
validDestroyLabelRequest,
|
||||||
validDestroyQueryRequest,
|
validDestroyQueryRequest,
|
||||||
validDestroyPackRequest,
|
validDestroyPackRequest,
|
||||||
validDestroyScheduledQueryRequest,
|
validDestroyScheduledQueryRequest,
|
||||||
@ -72,9 +73,10 @@ describe('Kolide - API client', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#createLabel', () => {
|
describe('labels', () => {
|
||||||
it('calls the appropriate endpoint with the correct parameters', (done) => {
|
const bearerToken = 'valid-bearer-token';
|
||||||
const bearerToken = 'valid-bearer-token';
|
|
||||||
|
it('#createLabel', (done) => {
|
||||||
const description = 'label description';
|
const description = 'label description';
|
||||||
const name = 'label name';
|
const name = 'label name';
|
||||||
const query = 'SELECT * FROM users';
|
const query = 'SELECT * FROM users';
|
||||||
@ -95,6 +97,20 @@ describe('Kolide - API client', () => {
|
|||||||
})
|
})
|
||||||
.catch(done);
|
.catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('#destroyLabel', (done) => {
|
||||||
|
const request = validDestroyLabelRequest(bearerToken, labelStub);
|
||||||
|
|
||||||
|
Kolide.setBearerToken(bearerToken);
|
||||||
|
Kolide.labels.destroy(labelStub)
|
||||||
|
.then(() => {
|
||||||
|
expect(request.isDone()).toEqual(true);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
throw new Error('Request should have been stubbed');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('configOptions', () => {
|
describe('configOptions', () => {
|
||||||
|
@ -21,7 +21,10 @@ import paths from 'router/paths';
|
|||||||
import QueryForm from 'components/forms/queries/QueryForm';
|
import QueryForm from 'components/forms/queries/QueryForm';
|
||||||
import QuerySidePanel from 'components/side_panels/QuerySidePanel';
|
import QuerySidePanel from 'components/side_panels/QuerySidePanel';
|
||||||
import Rocker from 'components/buttons/Rocker';
|
import Rocker from 'components/buttons/Rocker';
|
||||||
|
import Button from 'components/buttons/Button';
|
||||||
|
import Modal from 'components/modals/Modal';
|
||||||
import { selectOsqueryTable } from 'redux/nodes/components/QueryPages/actions';
|
import { selectOsqueryTable } from 'redux/nodes/components/QueryPages/actions';
|
||||||
|
import { renderFlash } from 'redux/nodes/notifications/actions';
|
||||||
import statusLabelsInterface from 'interfaces/status_labels';
|
import statusLabelsInterface from 'interfaces/status_labels';
|
||||||
import iconClassForLabel from 'utilities/icon_class_for_label';
|
import iconClassForLabel from 'utilities/icon_class_for_label';
|
||||||
import platformIconClass from 'utilities/platform_icon_class';
|
import platformIconClass from 'utilities/platform_icon_class';
|
||||||
@ -53,6 +56,7 @@ export class ManageHostsPage extends Component {
|
|||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
labelQueryText: '',
|
labelQueryText: '',
|
||||||
|
showDeleteModal: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,6 +141,27 @@ export class ManageHostsPage extends Component {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDeleteLabel = () => {
|
||||||
|
const { toggleModal } = this;
|
||||||
|
const { dispatch, selectedLabel } = this.props;
|
||||||
|
const { MANAGE_HOSTS } = paths;
|
||||||
|
|
||||||
|
return dispatch(labelActions.destroy(selectedLabel))
|
||||||
|
.then(() => {
|
||||||
|
toggleModal();
|
||||||
|
dispatch(push(MANAGE_HOSTS));
|
||||||
|
dispatch(renderFlash('success', 'Label successfully deleted'));
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleModal = () => {
|
||||||
|
const { showDeleteModal } = this.state;
|
||||||
|
|
||||||
|
this.setState({ showDeleteModal: !showDeleteModal });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
filterHosts = () => {
|
filterHosts = () => {
|
||||||
const { hosts, selectedLabel } = this.props;
|
const { hosts, selectedLabel } = this.props;
|
||||||
|
|
||||||
@ -150,6 +175,44 @@ export class ManageHostsPage extends Component {
|
|||||||
return orderedHosts;
|
return orderedHosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderModal = () => {
|
||||||
|
const { showDeleteModal } = this.state;
|
||||||
|
const { toggleModal, onDeleteLabel } = this;
|
||||||
|
|
||||||
|
if (!showDeleteModal) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title="Delete Label"
|
||||||
|
onExit={toggleModal}
|
||||||
|
className={`${baseClass}__modal`}
|
||||||
|
>
|
||||||
|
<p>Are you sure you wish to delete this label?</p>
|
||||||
|
<div>
|
||||||
|
<Button onClick={toggleModal} variant="inverse">Cancel</Button>
|
||||||
|
<Button onClick={onDeleteLabel} variant="alert">Delete</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDeleteButton = () => {
|
||||||
|
const { toggleModal } = this;
|
||||||
|
const { selectedLabel: { type } } = this.props;
|
||||||
|
|
||||||
|
if (type !== 'custom') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${baseClass}__delete-label`}>
|
||||||
|
<Button onClick={toggleModal} variant="alert">Delete</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderIcon = () => {
|
renderIcon = () => {
|
||||||
const { selectedLabel } = this.props;
|
const { selectedLabel } = this.props;
|
||||||
|
|
||||||
@ -188,7 +251,7 @@ export class ManageHostsPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderHeader = () => {
|
renderHeader = () => {
|
||||||
const { renderIcon, renderQuery } = this;
|
const { renderIcon, renderQuery, renderDeleteButton } = this;
|
||||||
const { display, isAddLabel, selectedLabel, statusLabels } = this.props;
|
const { display, isAddLabel, selectedLabel, statusLabels } = this.props;
|
||||||
|
|
||||||
if (!selectedLabel || isAddLabel) {
|
if (!selectedLabel || isAddLabel) {
|
||||||
@ -209,6 +272,8 @@ export class ManageHostsPage extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${baseClass}__header`}>
|
<div className={`${baseClass}__header`}>
|
||||||
|
{renderDeleteButton()}
|
||||||
|
|
||||||
<h1 className={`${baseClass}__title`}>
|
<h1 className={`${baseClass}__title`}>
|
||||||
{renderIcon()}
|
{renderIcon()}
|
||||||
<span>{displayText}</span>
|
<span>{displayText}</span>
|
||||||
@ -327,7 +392,7 @@ export class ManageHostsPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { renderForm, renderHeader, renderHosts, renderSidePanel } = this;
|
const { renderForm, renderHeader, renderHosts, renderSidePanel, renderModal } = this;
|
||||||
const { display, isAddLabel } = this.props;
|
const { display, isAddLabel } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -343,6 +408,7 @@ export class ManageHostsPage extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{renderSidePanel()}
|
{renderSidePanel()}
|
||||||
|
{renderModal()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import expect, { restoreSpies } from 'expect';
|
import expect, { spyOn, restoreSpies } from 'expect';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
|
|
||||||
|
import labelActions from 'redux/nodes/entities/labels/actions';
|
||||||
import ConnectedManageHostsPage, { ManageHostsPage } from 'pages/hosts/ManageHostsPage/ManageHostsPage';
|
import ConnectedManageHostsPage, { ManageHostsPage } from 'pages/hosts/ManageHostsPage/ManageHostsPage';
|
||||||
import { connectedComponent, createAceSpy, reduxMockStore, stubbedOsqueryTable } from 'test/helpers';
|
import { connectedComponent, createAceSpy, reduxMockStore, stubbedOsqueryTable } from 'test/helpers';
|
||||||
import { hostStub } from 'test/stubs';
|
import { hostStub } from 'test/stubs';
|
||||||
@ -11,6 +12,7 @@ const allHostsLabel = { id: 1, display_text: 'All Hosts', slug: 'all-hosts', typ
|
|||||||
const windowsLabel = { id: 2, display_text: 'Windows', slug: 'windows', type: 'platform', count: 22 };
|
const windowsLabel = { id: 2, display_text: 'Windows', slug: 'windows', type: 'platform', count: 22 };
|
||||||
const offlineHost = { ...hostStub, id: 111, status: 'offline' };
|
const offlineHost = { ...hostStub, id: 111, status: 'offline' };
|
||||||
const offlineHostsLabel = { id: 5, display_text: 'OFFLINE', slug: 'offline', status: 'offline', type: 'status', count: 1 };
|
const offlineHostsLabel = { id: 5, display_text: 'OFFLINE', slug: 'offline', status: 'offline', type: 'status', count: 1 };
|
||||||
|
const customLabel = { id: 6, display_text: 'Custom Label', slug: 'custom-label', type: 'custom', count: 3 };
|
||||||
const mockStore = reduxMockStore({
|
const mockStore = reduxMockStore({
|
||||||
components: {
|
components: {
|
||||||
ManageHostsPage: {
|
ManageHostsPage: {
|
||||||
@ -36,6 +38,7 @@ const mockStore = reduxMockStore({
|
|||||||
3: { id: 3, display_text: 'Ubuntu', slug: 'ubuntu', type: 'platform', count: 22 },
|
3: { id: 3, display_text: 'Ubuntu', slug: 'ubuntu', type: 'platform', count: 22 },
|
||||||
4: { id: 4, display_text: 'ONLINE', slug: 'online', type: 'status', count: 22 },
|
4: { id: 4, display_text: 'ONLINE', slug: 'online', type: 'status', count: 22 },
|
||||||
5: offlineHostsLabel,
|
5: offlineHostsLabel,
|
||||||
|
6: customLabel,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -182,4 +185,28 @@ describe('ManageHostsPage - component', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Delete a label', () => {
|
||||||
|
it('Deleted label after confirmation modal', () => {
|
||||||
|
const ownProps = { location: {}, params: { active_label: 'custom-label' } };
|
||||||
|
const component = connectedComponent(ConnectedManageHostsPage, { props: ownProps, mockStore });
|
||||||
|
const page = mount(component);
|
||||||
|
const deleteBtn = page.find('.manage-hosts__delete-label').find('button');
|
||||||
|
|
||||||
|
spyOn(labelActions, 'destroy').andCallThrough();
|
||||||
|
|
||||||
|
expect(page.find('Modal').length).toEqual(0);
|
||||||
|
|
||||||
|
deleteBtn.simulate('click');
|
||||||
|
|
||||||
|
const confirmModal = page.find('Modal');
|
||||||
|
|
||||||
|
expect(confirmModal.length).toEqual(1);
|
||||||
|
|
||||||
|
const confirmBtn = confirmModal.find('.button--alert');
|
||||||
|
confirmBtn.simulate('click');
|
||||||
|
|
||||||
|
expect(labelActions.destroy).toHaveBeenCalledWith(customLabel);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -27,6 +27,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__delete-label {
|
||||||
|
float: right;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
&__description {
|
&__description {
|
||||||
line-height: 1.54;
|
line-height: 1.54;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
|
@ -6,6 +6,7 @@ const { LABELS: schema } = schemas;
|
|||||||
|
|
||||||
export default reduxConfig({
|
export default reduxConfig({
|
||||||
createFunc: Kolide.createLabel,
|
createFunc: Kolide.createLabel,
|
||||||
|
destroyFunc: Kolide.labels.destroy,
|
||||||
entityName: 'labels',
|
entityName: 'labels',
|
||||||
loadAllFunc: Kolide.getLabels,
|
loadAllFunc: Kolide.getLabels,
|
||||||
parseEntityFunc: (label) => {
|
parseEntityFunc: (label) => {
|
||||||
|
@ -64,6 +64,16 @@ export const validCreateScheduledQueryRequest = (bearerToken, formData) => {
|
|||||||
.reply(201, { scheduled_query: scheduledQueryStub });
|
.reply(201, { scheduled_query: scheduledQueryStub });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const validDestroyLabelRequest = (bearerToken, label) => {
|
||||||
|
return nock('http://localhost:8080', {
|
||||||
|
reqHeaders: {
|
||||||
|
Authorization: `Bearer ${bearerToken}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.delete(`/api/v1/kolide/labels/${label.id}`)
|
||||||
|
.reply(200, {});
|
||||||
|
};
|
||||||
|
|
||||||
export const validDestroyQueryRequest = (bearerToken, query) => {
|
export const validDestroyQueryRequest = (bearerToken, query) => {
|
||||||
return nock('http://localhost:8080', {
|
return nock('http://localhost:8080', {
|
||||||
reqHeaders: {
|
reqHeaders: {
|
||||||
@ -398,6 +408,7 @@ export default {
|
|||||||
validCreatePackRequest,
|
validCreatePackRequest,
|
||||||
validCreateQueryRequest,
|
validCreateQueryRequest,
|
||||||
validCreateScheduledQueryRequest,
|
validCreateScheduledQueryRequest,
|
||||||
|
validDestroyLabelRequest,
|
||||||
validDestroyQueryRequest,
|
validDestroyQueryRequest,
|
||||||
validDestroyPackRequest,
|
validDestroyPackRequest,
|
||||||
validDestroyScheduledQueryRequest,
|
validDestroyScheduledQueryRequest,
|
||||||
|
Loading…
Reference in New Issue
Block a user