-
+ {renderCancelButton()}
diff --git a/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.tests.jsx b/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.tests.jsx
index b0e07a5c17..90b7dea39b 100644
--- a/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.tests.jsx
+++ b/frontend/components/forms/ConfigurePackQueryForm/ConfigurePackQueryForm.tests.jsx
@@ -5,6 +5,7 @@ import { noop } from 'lodash';
import ConfigurePackQueryForm from 'components/forms/ConfigurePackQueryForm';
import { itBehavesLikeAFormDropdownElement, itBehavesLikeAFormInputElement } from 'test/helpers';
+import { scheduledQueryStub } from 'test/stubs';
describe('ConfigurePackQueryForm - component', () => {
afterEach(restoreSpies);
@@ -53,4 +54,43 @@ describe('ConfigurePackQueryForm - component', () => {
});
});
});
+
+ describe('cancelling the form', () => {
+ const CancelButton = form => form.find('.configure-pack-query-form__cancel-btn');
+
+ it('displays a cancel Button when updating a scheduled query', () => {
+ const NewScheduledQueryForm = mount(
+
+ );
+ const UpdateScheduledQueryForm = mount(
+
+ );
+
+ expect(CancelButton(NewScheduledQueryForm).length).toEqual(0);
+ expect(CancelButton(UpdateScheduledQueryForm).length).toEqual(1);
+ });
+
+ it('calls the onCancel prop when the cancel Button is clicked', () => {
+ const spy = createSpy();
+ const UpdateScheduledQueryForm = mount(
+
+ );
+
+ CancelButton(UpdateScheduledQueryForm).simulate('click');
+
+ expect(spy).toHaveBeenCalledWith(scheduledQueryStub);
+ });
+ });
});
diff --git a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesList.jsx b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesList.jsx
index 6ae65a3f05..4be6d68ea6 100644
--- a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesList.jsx
+++ b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesList.jsx
@@ -12,10 +12,11 @@ const baseClass = 'scheduled-queries-list';
class ScheduledQueriesList extends Component {
static propTypes = {
isScheduledQueriesAvailable: PropTypes.bool,
- onSelectAllQueries: PropTypes.func.isRequired,
+ onCheckAllQueries: PropTypes.func.isRequired,
+ onCheckQuery: PropTypes.func.isRequired,
onSelectQuery: PropTypes.func.isRequired,
scheduledQueries: PropTypes.arrayOf(queryInterface).isRequired,
- selectedScheduledQueryIDs: PropTypes.arrayOf(PropTypes.number).isRequired,
+ checkedScheduledQueryIDs: PropTypes.arrayOf(PropTypes.number).isRequired,
};
constructor (props) {
@@ -25,23 +26,23 @@ class ScheduledQueriesList extends Component {
}
isChecked = (scheduledQuery) => {
- const { selectedScheduledQueryIDs } = this.props;
+ const { checkedScheduledQueryIDs } = this.props;
const { allQueriesSelected } = this.state;
if (allQueriesSelected) {
return true;
}
- return includes(selectedScheduledQueryIDs, scheduledQuery.id);
+ return includes(checkedScheduledQueryIDs, scheduledQuery.id);
}
handleSelectAllQueries = (shouldSelectAllQueries) => {
- const { onSelectAllQueries } = this.props;
+ const { onCheckAllQueries } = this.props;
const { allQueriesSelected } = this.state;
this.setState({ allQueriesSelected: !allQueriesSelected });
- return onSelectAllQueries(shouldSelectAllQueries);
+ return onCheckAllQueries(shouldSelectAllQueries);
}
renderHelpText = () => {
@@ -83,12 +84,12 @@ class ScheduledQueriesList extends Component {
}
render () {
- const { onSelectQuery, scheduledQueries, selectedScheduledQueryIDs } = this.props;
+ const { onCheckQuery, onSelectQuery, scheduledQueries, checkedScheduledQueryIDs } = this.props;
const { allQueriesSelected } = this.state;
const { renderHelpText, handleSelectAllQueries } = this;
const wrapperClassName = classnames(`${baseClass}__table`, {
- [`${baseClass}__table--query-selected`]: size(selectedScheduledQueryIDs),
+ [`${baseClass}__table--query-selected`]: size(checkedScheduledQueryIDs),
});
return (
@@ -116,6 +117,7 @@ class ScheduledQueriesList extends Component {
diff --git a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx
index 7277e59f64..a10d81b3a1 100644
--- a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx
+++ b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.jsx
@@ -1,6 +1,7 @@
import React, { Component, PropTypes } from 'react';
import Checkbox from 'components/forms/fields/Checkbox';
+import ClickableTableRow from 'components/ClickableTableRow';
import Icon from 'components/icons/Icon';
import PlatformIcon from 'components/icons/PlatformIcon';
import { isEqual } from 'lodash';
@@ -10,6 +11,7 @@ class ScheduledQueriesListItem extends Component {
static propTypes = {
checked: PropTypes.bool,
disabled: PropTypes.bool,
+ onCheck: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
scheduledQuery: scheduledQueryInterface.isRequired,
};
@@ -23,9 +25,15 @@ class ScheduledQueriesListItem extends Component {
}
onCheck = (value) => {
+ const { onCheck, scheduledQuery } = this.props;
+
+ return onCheck(value, scheduledQuery.id);
+ }
+
+ onSelect = () => {
const { onSelect, scheduledQuery } = this.props;
- return onSelect(value, scheduledQuery.id);
+ return onSelect(scheduledQuery);
}
loggingTypeString = () => {
@@ -55,12 +63,11 @@ class ScheduledQueriesListItem extends Component {
render () {
const { checked, disabled, scheduledQuery } = this.props;
- const { onCheck, renderPlatformIcon } = this;
const { id, name, interval, shard, version } = scheduledQuery;
- const { loggingTypeString } = this;
+ const { loggingTypeString, onCheck, onSelect, renderPlatformIcon } = this;
return (
-
+
|
{version ? `${version}+` : 'Any'} |
{shard} |
|
-
+
);
}
}
diff --git a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.tests.jsx b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.tests.jsx
index a46ae32cd9..d2268b7794 100644
--- a/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.tests.jsx
+++ b/frontend/components/queries/ScheduledQueriesList/ScheduledQueriesListItem/ScheduledQueriesListItem.tests.jsx
@@ -6,11 +6,18 @@ import { noop } from 'lodash';
import { scheduledQueryStub } from 'test/stubs';
import ScheduledQueriesListItem from './index';
+const defaultProps = {
+ checked: false,
+ onCheck: noop,
+ onSelect: noop,
+ scheduledQuery: scheduledQueryStub,
+};
+
describe('ScheduledQueriesListItem - component', () => {
afterEach(restoreSpies);
it('renders the scheduled query data', () => {
- const component = mount(
);
+ const component = mount(
);
expect(component.text()).toInclude(scheduledQueryStub.name);
expect(component.text()).toInclude(scheduledQueryStub.interval);
expect(component.text()).toInclude(scheduledQueryStub.shard);
@@ -18,17 +25,29 @@ describe('ScheduledQueriesListItem - component', () => {
});
it('renders a Checkbox component', () => {
- const component = mount(
);
+ const component = mount(
);
expect(component.find('Checkbox').length).toEqual(1);
});
- it('calls the onSelect prop when a checkbox is changed', () => {
- const onSelectSpy = createSpy();
- const component = mount(
);
+ it('calls the onCheck prop when a checkbox is changed', () => {
+ const onCheckSpy = createSpy();
+ const props = { ...defaultProps, onCheck: onCheckSpy };
+ const component = mount(
);
const checkbox = component.find('Checkbox').first();
checkbox.find('input').simulate('change');
- expect(onSelectSpy).toHaveBeenCalledWith(true, scheduledQueryStub.id);
+ expect(onCheckSpy).toHaveBeenCalledWith(true, scheduledQueryStub.id);
+ });
+
+ it('calls the onSelect prop when a list item is selected', () => {
+ const spy = createSpy();
+ const props = { ...defaultProps, onSelect: spy };
+ const component = mount(
);
+ const tableRow = component.find('ClickableTableRow');
+
+ tableRow.simulate('click');
+
+ expect(spy).toHaveBeenCalledWith(scheduledQueryStub);
});
});
diff --git a/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.jsx b/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.jsx
index 852a304247..ccb0a377a8 100644
--- a/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.jsx
+++ b/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.jsx
@@ -14,6 +14,7 @@ class ScheduledQueriesListWrapper extends Component {
static propTypes = {
onRemoveScheduledQueries: PropTypes.func,
onScheduledQueryFormSubmit: PropTypes.func,
+ onSelectScheduledQuery: PropTypes.func,
scheduledQueries: PropTypes.arrayOf(queryInterface),
};
@@ -22,8 +23,7 @@ class ScheduledQueriesListWrapper extends Component {
this.state = {
querySearchText: '',
- selectAll: false,
- selectedScheduledQueryIDs: [],
+ checkedScheduledQueryIDs: [],
};
}
@@ -31,35 +31,35 @@ class ScheduledQueriesListWrapper extends Component {
evt.preventDefault();
const { onRemoveScheduledQueries: handleRemoveScheduledQueries } = this.props;
- const { selectedScheduledQueryIDs } = this.state;
+ const { checkedScheduledQueryIDs } = this.state;
- this.setState({ selectedScheduledQueryIDs: [] });
+ this.setState({ checkedScheduledQueryIDs: [] });
- return handleRemoveScheduledQueries(selectedScheduledQueryIDs);
+ return handleRemoveScheduledQueries(checkedScheduledQueryIDs);
}
- onSelectAllQueries = (shouldSelectAll) => {
- if (shouldSelectAll) {
+ onCheckAllQueries = (shouldCheckAll) => {
+ if (shouldCheckAll) {
const allScheduledQueries = this.getQueries();
- const selectedScheduledQueryIDs = allScheduledQueries.map(sq => sq.id);
+ const checkedScheduledQueryIDs = allScheduledQueries.map(sq => sq.id);
- this.setState({ selectedScheduledQueryIDs });
+ this.setState({ checkedScheduledQueryIDs });
return false;
}
- this.setState({ selectedScheduledQueryIDs: [] });
+ this.setState({ checkedScheduledQueryIDs: [] });
return false;
}
- onSelectQuery = (shouldAddQuery, scheduledQueryID) => {
- const { selectedScheduledQueryIDs } = this.state;
- const newSelectedScheduledQueryIDs = shouldAddQuery ?
- selectedScheduledQueryIDs.concat(scheduledQueryID) :
- pull(selectedScheduledQueryIDs, scheduledQueryID);
+ onCheckQuery = (shouldCheckQuery, scheduledQueryID) => {
+ const { checkedScheduledQueryIDs } = this.state;
+ const newCheckedScheduledQueryIDs = shouldCheckQuery ?
+ checkedScheduledQueryIDs.concat(scheduledQueryID) :
+ pull(checkedScheduledQueryIDs, scheduledQueryID);
- this.setState({ selectedScheduledQueryIDs: newSelectedScheduledQueryIDs });
+ this.setState({ checkedScheduledQueryIDs: newCheckedScheduledQueryIDs });
return false;
}
@@ -77,9 +77,9 @@ class ScheduledQueriesListWrapper extends Component {
renderButton = () => {
const { onRemoveScheduledQueries } = this;
- const { selectedScheduledQueryIDs } = this.state;
+ const { checkedScheduledQueryIDs } = this.state;
- const scheduledQueryCount = selectedScheduledQueryIDs.length;
+ const scheduledQueryCount = checkedScheduledQueryIDs.length;
if (scheduledQueryCount) {
const queryText = scheduledQueryCount === 1 ? 'Query' : 'Queries';
@@ -107,19 +107,20 @@ class ScheduledQueriesListWrapper extends Component {
}
renderQueriesList = () => {
- const { getQueries, onHidePackForm, onSelectAllQueries, onSelectQuery } = this;
- const { onScheduledQueryFormSubmit, scheduledQueries } = this.props;
- const { selectedScheduledQueryIDs } = this.state;
+ const { getQueries, onHidePackForm, onCheckAllQueries, onCheckQuery } = this;
+ const { onScheduledQueryFormSubmit, onSelectScheduledQuery, scheduledQueries } = this.props;
+ const { checkedScheduledQueryIDs } = this.state;
return (
diff --git a/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.tests.jsx b/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.tests.jsx
index 920bbf07d1..c59a17a250 100644
--- a/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.tests.jsx
+++ b/frontend/components/queries/ScheduledQueriesListWrapper/ScheduledQueriesListWrapper.tests.jsx
@@ -1,27 +1,28 @@
import React from 'react';
import expect, { createSpy, restoreSpies } from 'expect';
import { mount } from 'enzyme';
+import { noop } from 'lodash';
-import { queryStub, scheduledQueryStub } from 'test/stubs';
+import { scheduledQueryStub } from 'test/stubs';
import { fillInFormInput } from 'test/helpers';
import ScheduledQueriesListWrapper from './index';
-const allQueries = [queryStub];
const scheduledQueries = [
scheduledQueryStub,
{ ...scheduledQueryStub, id: 100, name: 'mac hosts' },
];
+const defaultProps = {
+ onRemoveScheduledQueries: noop,
+ onScheduledQueryFormSubmit: noop,
+ onSelectScheduledQuery: noop,
+ scheduledQueries,
+};
describe('ScheduledQueriesListWrapper - component', () => {
afterEach(restoreSpies);
it('renders the "Remove Query" button when queries have been selected', () => {
- const component = mount(
-
- );
+ const component = mount(
);
component.find('Checkbox').last().find('input').simulate('change');
@@ -34,15 +35,14 @@ describe('ScheduledQueriesListWrapper - component', () => {
it('calls the onRemoveScheduledQueries prop', () => {
const spy = createSpy();
- const component = mount(
-
- );
+ const props = { ...defaultProps, onRemoveScheduledQueries: spy };
+ const component = mount(
);
- component.find('Checkbox').last().find('input').simulate('change');
+ component
+ .find('Checkbox')
+ .find({ name: `scheduled-query-checkbox-${scheduledQueryStub.id}` })
+ .find('input')
+ .simulate('change');
const removeQueryBtn = component.find('Button').find({ children: ['Remove ', 'Query'] });
@@ -52,12 +52,7 @@ describe('ScheduledQueriesListWrapper - component', () => {
});
it('filters queries', () => {
- const component = mount(
-
- );
+ const component = mount(
);
const searchQueriesInput = component.find({ name: 'search-queries' });
const QueriesList = component.find('ScheduledQueriesList');
@@ -71,20 +66,15 @@ describe('ScheduledQueriesListWrapper - component', () => {
it('allows selecting all scheduled queries at once', () => {
const allScheduledQueryIDs = scheduledQueries.map(sq => sq.id);
- const component = mount(
-
- );
+ const component = mount(
);
const selectAllCheckbox = component.find({ name: 'select-all-scheduled-queries' });
selectAllCheckbox.simulate('change');
- expect(component.state('selectedScheduledQueryIDs')).toEqual(allScheduledQueryIDs);
+ expect(component.state('checkedScheduledQueryIDs')).toEqual(allScheduledQueryIDs);
selectAllCheckbox.simulate('change');
- expect(component.state('selectedScheduledQueryIDs')).toEqual([]);
+ expect(component.state('checkedScheduledQueryIDs')).toEqual([]);
});
});
diff --git a/frontend/components/side_panels/ScheduleQuerySidePanel/ScheduleQuerySidePanel.jsx b/frontend/components/side_panels/ScheduleQuerySidePanel/ScheduleQuerySidePanel.jsx
index 4dfaeca45e..421400e4bf 100644
--- a/frontend/components/side_panels/ScheduleQuerySidePanel/ScheduleQuerySidePanel.jsx
+++ b/frontend/components/side_panels/ScheduleQuerySidePanel/ScheduleQuerySidePanel.jsx
@@ -2,23 +2,37 @@ import React, { PropTypes } from 'react';
import ConfigurePackQueryForm from 'components/forms/ConfigurePackQueryForm';
import queryInterface from 'interfaces/query';
+import scheduledQueryInterface from 'interfaces/scheduled_query';
import SearchPackQuery from './SearchPackQuery';
import SecondarySidePanelContainer from '../SecondarySidePanelContainer';
const baseClass = 'schedule-query-side-panel';
-const ScheduleQuerySidePanel = ({ allQueries, onConfigurePackQuerySubmit, onSelectQuery, selectedQuery }) => {
+const ScheduleQuerySidePanel = ({
+ allQueries,
+ onConfigurePackQuerySubmit,
+ onFormCancel,
+ onUpdateScheduledQuery,
+ onSelectQuery,
+ selectedQuery,
+ selectedScheduledQuery,
+}) => {
const renderForm = () => {
if (!selectedQuery) {
return false;
}
- const formData = { query_id: selectedQuery.id };
+ const formData = selectedScheduledQuery || {};
+
+ formData.query_id = selectedQuery.id;
+
+ const handleSubmit = selectedScheduledQuery ? onUpdateScheduledQuery : onConfigurePackQuerySubmit;
return (
);
};
@@ -38,8 +52,11 @@ const ScheduleQuerySidePanel = ({ allQueries, onConfigurePackQuerySubmit, onSele
ScheduleQuerySidePanel.propTypes = {
allQueries: PropTypes.arrayOf(queryInterface),
onConfigurePackQuerySubmit: PropTypes.func,
+ onFormCancel: PropTypes.func,
onSelectQuery: PropTypes.func,
+ onUpdateScheduledQuery: PropTypes.func,
selectedQuery: queryInterface,
+ selectedScheduledQuery: scheduledQueryInterface,
};
export default ScheduleQuerySidePanel;
diff --git a/frontend/kolide/endpoints.js b/frontend/kolide/endpoints.js
index cee0180fdd..cd48a8b5dc 100644
--- a/frontend/kolide/endpoints.js
+++ b/frontend/kolide/endpoints.js
@@ -21,7 +21,8 @@ export default {
QUERIES: '/v1/kolide/queries',
RESET_PASSWORD: '/v1/kolide/reset_password',
RUN_QUERY: '/v1/kolide/queries/run',
- SCHEDULED_QUERIES: (pack) => {
+ SCHEDULED_QUERIES: '/v1/kolide/schedule',
+ SCHEDULED_QUERY: (pack) => {
return `/v1/kolide/packs/${pack.id}/scheduled`;
},
SETUP: '/v1/setup',
diff --git a/frontend/kolide/helpers.js b/frontend/kolide/helpers.js
index 954b779589..aedbe65ae9 100644
--- a/frontend/kolide/helpers.js
+++ b/frontend/kolide/helpers.js
@@ -1,4 +1,4 @@
-import { flatMap, kebabCase, pick, size } from 'lodash';
+import { flatMap, kebabCase, omit, pick, size } from 'lodash';
import md5 from 'js-md5';
const ORG_INFO_ATTRS = ['org_name', 'org_logo_url'];
@@ -69,6 +69,61 @@ const parseLicense = (license) => {
return { ...license, allowed_hosts: allowedHosts };
};
+export const formatScheduledQueryForServer = (scheduledQuery) => {
+ const {
+ interval,
+ logging_type: loggingType,
+ pack_id: packID,
+ platform,
+ query_id: queryID,
+ shard,
+ } = scheduledQuery;
+ const result = omit(scheduledQuery, ['logging_type']);
+
+ if (platform === 'all') {
+ result.platform = '';
+ }
+
+ if (interval) {
+ result.interval = Number(interval);
+ }
+
+ if (loggingType) {
+ result.removed = loggingType === 'differential';
+ result.snapshot = loggingType === 'snapshot';
+ }
+
+ if (packID) {
+ result.pack_id = Number(packID);
+ }
+
+ if (queryID) {
+ result.query_id = Number(queryID);
+ }
+
+ if (shard) {
+ result.shard = Number(shard);
+ }
+
+ return result;
+};
+
+export const formatScheduledQueryForClient = (scheduledQuery) => {
+ if (scheduledQuery.platform === '') {
+ scheduledQuery.platform = 'all';
+ }
+
+ if (scheduledQuery.snapshot) {
+ scheduledQuery.logging_type = 'snapshot';
+ } else if (scheduledQuery.removed) {
+ scheduledQuery.logging_type = 'differential';
+ } else {
+ scheduledQuery.logging_type = 'differential_ignore_removals';
+ }
+
+ return scheduledQuery;
+};
+
const setupData = (formData) => {
const orgInfo = pick(formData, ORG_INFO_ATTRS);
const adminInfo = pick(formData, ADMIN_ATTRS);
@@ -88,6 +143,8 @@ const setupData = (formData) => {
export default {
addGravatarUrlToResource,
formatConfigDataForServer,
+ formatScheduledQueryForClient,
+ formatScheduledQueryForServer,
formatSelectedTargetsForApi,
labelSlug,
parseLicense,
diff --git a/frontend/kolide/helpers.tests.js b/frontend/kolide/helpers.tests.js
index e30142d355..47e93ce9fa 100644
--- a/frontend/kolide/helpers.tests.js
+++ b/frontend/kolide/helpers.tests.js
@@ -1,7 +1,7 @@
import expect from 'expect';
import { omit } from 'lodash';
-import { configStub, licenseStub } from 'test/stubs';
+import { configStub, licenseStub, scheduledQueryStub } from 'test/stubs';
import helpers from 'kolide/helpers';
const label1 = { id: 1, target_type: 'labels' };
@@ -76,6 +76,69 @@ describe('Kolide API - helpers', () => {
});
});
+ describe('#formatScheduledQueryForServer', () => {
+ const { formatScheduledQueryForServer } = helpers;
+ const scheduledQuery = {
+ ...scheduledQueryStub,
+ logging_type: 'snapshot',
+ pack_id: '3',
+ platform: 'all',
+ query_id: '1',
+ shard: '12',
+ };
+
+ it('sets the correct attributes for the server', () => {
+ expect(formatScheduledQueryForServer(scheduledQuery)).toEqual({
+ ...scheduledQueryStub,
+ pack_id: 3,
+ platform: '',
+ query_id: 1,
+ shard: 12,
+ snapshot: true,
+ removed: false,
+ });
+ });
+ });
+
+ describe('#formatScheduledQueryForClient', () => {
+ const { formatScheduledQueryForClient } = helpers;
+ const scheduledQuery = {
+ ...scheduledQueryStub,
+ platform: '',
+ snapshot: true,
+ };
+
+ it('sets the correct attributes for the server', () => {
+ expect(formatScheduledQueryForClient(scheduledQuery)).toEqual({
+ ...scheduledQueryStub,
+ logging_type: 'snapshot',
+ platform: 'all',
+ });
+ });
+
+ it('sets the logging_type attribute', () => {
+ expect(formatScheduledQueryForClient({ ...scheduledQueryStub, removed: true, snapshot: false })).toEqual({
+ ...scheduledQueryStub,
+ logging_type: 'differential',
+ removed: true,
+ snapshot: false,
+ });
+
+ expect(formatScheduledQueryForClient({ ...scheduledQueryStub, snapshot: true })).toEqual({
+ ...scheduledQueryStub,
+ logging_type: 'snapshot',
+ snapshot: true,
+ });
+
+ expect(formatScheduledQueryForClient({ ...scheduledQueryStub, snapshot: false, removed: false })).toEqual({
+ ...scheduledQueryStub,
+ logging_type: 'differential_ignore_removals',
+ removed: false,
+ snapshot: false,
+ });
+ });
+ });
+
describe('#formatSelectedTargetsForApi', () => {
const { formatSelectedTargetsForApi } = helpers;
diff --git a/frontend/kolide/index.js b/frontend/kolide/index.js
index b334fc8cf1..b9b2b69c7a 100644
--- a/frontend/kolide/index.js
+++ b/frontend/kolide/index.js
@@ -177,6 +177,50 @@ class Kolide extends Base {
},
}
+ scheduledQueries = {
+ create: (formData) => {
+ const { SCHEDULED_QUERIES } = endpoints;
+ const { interval, logging_type: loggingType, pack_id: packID, platform, query_id: queryID, shard, version } = formData;
+ const removed = loggingType === 'differential';
+ const snapshot = loggingType === 'snapshot';
+
+ const params = {
+ interval: Number(interval),
+ pack_id: Number(packID),
+ platform,
+ query_id: Number(queryID),
+ removed,
+ snapshot,
+ shard: Number(shard),
+ version,
+ };
+
+ return this.authenticatedPost(this.endpoint(SCHEDULED_QUERIES), JSON.stringify(params))
+ .then(response => response.scheduled);
+ },
+ destroy: ({ id }) => {
+ const { SCHEDULED_QUERIES } = endpoints;
+ const endpoint = `${this.endpoint(SCHEDULED_QUERIES)}/${id}`;
+
+ return this.authenticatedDelete(endpoint);
+ },
+ loadAll: (pack) => {
+ const { SCHEDULED_QUERY } = endpoints;
+ const scheduledQueryPath = SCHEDULED_QUERY(pack);
+
+ return this.authenticatedGet(this.endpoint(scheduledQueryPath))
+ .then(response => response.scheduled);
+ },
+ update: (scheduledQuery, updatedAttributes) => {
+ const { SCHEDULED_QUERIES } = endpoints;
+ const endpoint = this.endpoint(`${SCHEDULED_QUERIES}/${scheduledQuery.id}`);
+ const params = helpers.formatScheduledQueryForServer(updatedAttributes);
+
+ return this.authenticatedPatch(endpoint, JSON.stringify(params))
+ .then(response => response.scheduled);
+ },
+ }
+
users = {
changePassword: (passwordParams) => {
const { CHANGE_PASSWORD } = endpoints;
@@ -236,25 +280,6 @@ class Kolide extends Base {
.then((response) => { return response.query; });
}
- createScheduledQuery = ({ interval, logging_type: loggingType, pack_id: packID, platform, query_id: queryID, shard, version }) => {
- const removed = loggingType === 'differential';
- const snapshot = loggingType === 'snapshot';
-
- const formData = {
- interval: Number(interval),
- pack_id: Number(packID),
- platform,
- query_id: Number(queryID),
- removed,
- snapshot,
- shard: Number(shard),
- version,
- };
-
- return this.authenticatedPost(this.endpoint('/v1/kolide/schedule'), JSON.stringify(formData))
- .then(response => response.scheduled);
- }
-
destroyQuery = ({ id }) => {
const { QUERIES } = endpoints;
const endpoint = `${this.endpoint(QUERIES)}/${id}`;
@@ -269,12 +294,6 @@ class Kolide extends Base {
return this.authenticatedDelete(endpoint);
}
- destroyScheduledQuery = ({ id }) => {
- const endpoint = `${this.endpoint('/v1/kolide/schedule')}/${id}`;
-
- return this.authenticatedDelete(endpoint);
- }
-
createUser = (formData) => {
const { USERS } = endpoints;
@@ -397,14 +416,6 @@ class Kolide extends Base {
.then((response) => { return response.packs; });
}
- getScheduledQueries = (pack) => {
- const { SCHEDULED_QUERIES } = endpoints;
- const scheduledQueryPath = SCHEDULED_QUERIES(pack);
-
- return this.authenticatedGet(this.endpoint(scheduledQueryPath))
- .then(response => response.scheduled);
- }
-
getUsers = () => {
const { USERS } = endpoints;
diff --git a/frontend/kolide/index.tests.js b/frontend/kolide/index.tests.js
index 42dbe97c72..1985ad7467 100644
--- a/frontend/kolide/index.tests.js
+++ b/frontend/kolide/index.tests.js
@@ -11,6 +11,7 @@ import {
licenseStub,
packStub,
queryStub,
+ scheduledQueryStub,
userStub,
} from 'test/stubs';
@@ -56,6 +57,7 @@ const {
validUpdateLabelRequest,
validUpdatePackRequest,
validUpdateQueryRequest,
+ validUpdateScheduledQueriesRequest,
validUpdateUserRequest,
validUser,
} = mocks;
@@ -455,6 +457,75 @@ describe('Kolide - API client', () => {
});
});
+ describe('scheduledQueries', () => {
+ describe('#create', () => {
+ it('calls the appropriate endpoint with the correct parameters', (done) => {
+ const formData = {
+ interval: 60,
+ logging_type: 'differential',
+ pack_id: 1,
+ platform: 'darwin',
+ query_id: 2,
+ shard: 12,
+ };
+ const request = validCreateScheduledQueryRequest(bearerToken, formData);
+
+ Kolide.setBearerToken(bearerToken);
+ Kolide.scheduledQueries.create(formData)
+ .then(() => {
+ expect(request.isDone()).toEqual(true);
+ done();
+ })
+ .catch(done);
+ });
+ });
+
+ describe('#destroy', () => {
+ it('calls the appropriate endpoint with the correct parameters', (done) => {
+ const scheduledQuery = { id: 1 };
+ const request = validDestroyScheduledQueryRequest(bearerToken, scheduledQuery);
+
+ Kolide.setBearerToken(bearerToken);
+ Kolide.scheduledQueries.destroy(scheduledQuery)
+ .then(() => {
+ expect(request.isDone()).toEqual(true);
+ done();
+ })
+ .catch(done);
+ });
+ });
+
+ describe('#loadAll', () => {
+ it('calls the appropriate endpoint with the correct parameters', (done) => {
+ const pack = { id: 1 };
+ const request = validGetScheduledQueriesRequest(bearerToken, pack);
+
+ Kolide.setBearerToken(bearerToken);
+ Kolide.scheduledQueries.loadAll(pack)
+ .then(() => {
+ expect(request.isDone()).toEqual(true);
+ done();
+ })
+ .catch(done);
+ });
+ });
+
+ describe('#update', () => {
+ it('calls the appropriate endpoint with the correct parameters', (done) => {
+ const updatedAttrs = { interval: 200 };
+ const request = validUpdateScheduledQueriesRequest(bearerToken, scheduledQueryStub, updatedAttrs);
+
+ Kolide.setBearerToken(bearerToken);
+ Kolide.scheduledQueries.update(scheduledQueryStub, updatedAttrs)
+ .then(() => {
+ expect(request.isDone()).toEqual(true);
+ done();
+ })
+ .catch(done);
+ });
+ });
+ });
+
describe('users', () => {
describe('#changePassword', () => {
it('calls the appropriate endpoint with the correct parameters', (done) => {
@@ -535,58 +606,6 @@ describe('Kolide - API client', () => {
});
});
- describe('#createScheduledQuery', () => {
- it('calls the appropriate endpoint with the correct parameters', (done) => {
- const formData = {
- interval: 60,
- logging_type: 'differential',
- pack_id: 1,
- platform: 'darwin',
- query_id: 2,
- shard: 12,
- };
- const request = validCreateScheduledQueryRequest(bearerToken, formData);
-
- Kolide.setBearerToken(bearerToken);
- Kolide.createScheduledQuery(formData)
- .then(() => {
- expect(request.isDone()).toEqual(true);
- done();
- })
- .catch(done);
- });
- });
-
- describe('#destroyScheduledQuery', () => {
- it('calls the appropriate endpoint with the correct parameters', (done) => {
- const scheduledQuery = { id: 1 };
- const request = validDestroyScheduledQueryRequest(bearerToken, scheduledQuery);
-
- Kolide.setBearerToken(bearerToken);
- Kolide.destroyScheduledQuery(scheduledQuery)
- .then(() => {
- expect(request.isDone()).toEqual(true);
- done();
- })
- .catch(done);
- });
- });
-
- describe('#getScheduledQueries', () => {
- it('calls the appropriate endpoint with the correct parameters', (done) => {
- const pack = { id: 1 };
- const request = validGetScheduledQueriesRequest(bearerToken, pack);
-
- Kolide.setBearerToken(bearerToken);
- Kolide.getScheduledQueries(pack)
- .then(() => {
- expect(request.isDone()).toEqual(true);
- done();
- })
- .catch(done);
- });
- });
-
describe('#getTargets', () => {
it('correctly parses the response', (done) => {
nock.cleanAll();
diff --git a/frontend/pages/packs/EditPackPage/EditPackPage.jsx b/frontend/pages/packs/EditPackPage/EditPackPage.jsx
index 21ff22e08b..67756a858c 100644
--- a/frontend/pages/packs/EditPackPage/EditPackPage.jsx
+++ b/frontend/pages/packs/EditPackPage/EditPackPage.jsx
@@ -43,6 +43,8 @@ export class EditPackPage extends Component {
super(props);
this.state = {
+ selectedQuery: null,
+ selectedScheduledQuery: null,
targetsCount: 0,
};
}
@@ -118,14 +120,27 @@ export class EditPackPage extends Component {
return false;
}
- onSelectQuery = (query) => {
+ onSelectQuery = (queryID) => {
const { allQueries } = this.props;
- const selectedQuery = find(allQueries, { id: Number(query) });
+ const selectedQuery = find(allQueries, { id: Number(queryID) });
this.setState({ selectedQuery });
return false;
}
+ onSelectScheduledQuery = (scheduledQuery) => {
+ const { selectedScheduledQuery } = this.state;
+
+ if (isEqual(scheduledQuery, selectedScheduledQuery)) {
+ this.setState({ selectedScheduledQuery: null, selectedQuery: null });
+ } else {
+ this.onSelectQuery(scheduledQuery.query_id);
+ this.setState({ selectedScheduledQuery: scheduledQuery });
+ }
+
+ return false;
+ }
+
onToggleEdit = () => {
const { dispatch, isEdit, packID } = this.props;
@@ -136,6 +151,22 @@ export class EditPackPage extends Component {
return dispatch(push(`/packs/${packID}/edit`));
}
+ onUpdateScheduledQuery = (formData) => {
+ const { dispatch } = this.props;
+ const { selectedScheduledQuery } = this.state;
+ const { update } = scheduledQueryActions;
+ const updatedAttrs = deepDifference(formData, selectedScheduledQuery);
+
+ dispatch(update(selectedScheduledQuery, updatedAttrs))
+ .then(() => {
+ this.setState({ selectedScheduledQuery: null, selectedQuery: null });
+ dispatch(renderFlash('success', 'Scheduled Query updated!'));
+ })
+ .catch(() => {
+ dispatch(renderFlash('error', 'Unable to update your Scheduled Query.'));
+ });
+ }
+
handlePackFormSubmit = (formData) => {
const { dispatch, pack } = this.props;
const { update } = packActions;
@@ -164,12 +195,12 @@ export class EditPackPage extends Component {
const { dispatch, packID } = this.props;
const scheduledQueryData = {
...formData,
- snapshot: formData.logging_type === 'snapshot',
pack_id: packID,
};
dispatch(create(scheduledQueryData))
.then(() => {
+ this.setState({ selectedScheduledQuery: null, selectedQuery: null });
dispatch(renderFlash('success', 'Query scheduled!'));
})
.catch(() => {
@@ -188,9 +219,11 @@ export class EditPackPage extends Component {
onCancelEditPack,
onFetchTargets,
onSelectQuery,
+ onSelectScheduledQuery,
onToggleEdit,
+ onUpdateScheduledQuery,
} = this;
- const { targetsCount, selectedQuery } = this.state;
+ const { targetsCount, selectedQuery, selectedScheduledQuery } = this.state;
const {
allQueries,
isEdit,
@@ -225,14 +258,18 @@ export class EditPackPage extends Component {