Skip to content

Commit 17a19b0

Browse files
authored
* Compass 810 extended query explain (#819) * add extended query bar to explain tab + use bumped data-service * add support for get indexes from explain view * fix incorrect option key for projection * add support + test for projection from explain * fix tests for travis issues * rebase on master + fix explain sort test # Conflicts: # test/functional/support/packages/spectron-explain.js * added missing spectron helpers. * fix linter errors * adding clickExplainViewDetails spectron helper. * add extended query bar to explain tab + use bumped data-service * add support for get indexes from explain view * fix incorrect option key for projection * add support + test for projection from explain * fix tests for travis issues * rebase on master + fix explain sort test # Conflicts: # test/functional/support/packages/spectron-explain.js * add getExplainRawJSONDocument spectron helper.
1 parent dc5445f commit 17a19b0

File tree

8 files changed

+171
-31
lines changed

8 files changed

+171
-31
lines changed

src/internal-packages/explain/lib/components/compass-explain.jsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ const ExplainBody = require('./explain-body');
44
const ViewSwitcher = require('./shared/view-switcher');
55
const ExplainActions = require('../actions');
66

7-
// TODO (thomasr) data-service explain does not pass through options to find yet.
8-
const QUERYBAR_LAYOUT = ['filter'];
9-
107
const READ_ONLY_WARNING = 'Explain plans on readonly views are not supported.';
118

129
const COLLECTION_SCAN_WARNING = 'To prevent unintended collection scans, please'
@@ -85,13 +82,14 @@ class CompassExplain extends React.Component {
8582
return (
8683
<div className="compass-explain">
8784
<div className="controls-container">
88-
<this.queryBar layout={QUERYBAR_LAYOUT} />
85+
<this.queryBar />
8986
<div className="action-bar">
9087
<ViewSwitcher
9188
label="View Details As"
9289
buttonLabels={['Visual Tree', 'Raw JSON']}
9390
activeButton={activeViewTypeButton}
9491
disabled={isDisabled}
92+
dataTestId="explain-view"
9593
onClick={this.onViewSwitch}
9694
/>
9795
</div>

src/internal-packages/explain/lib/components/explain-summary.jsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class ExplainSummary extends React.Component {
6363
/>
6464
<SummaryIndexStat
6565
dataLink={HELP_URLS.INDEX_USED}
66+
dataTestId="explain-index-stats"
6667
indexType={this.props.indexType}
6768
index={this.props.index}
6869
/>

src/internal-packages/explain/lib/components/shared/view-switcher.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ class ViewSwitcher extends React.Component {
1313
buttonFactory() {
1414
return _.map(this.props.buttonLabels, (label) => {
1515
const active = this.props.activeButton === label;
16+
const dataTestId = `${this.props.dataTestId}-${label.toLowerCase().replace(/ /g, '-')}`;
1617
return (
17-
<Button key={label} active={active} disabled={this.props.disabled} onClick={this.props.onClick.bind(this, label)} bsSize="xsmall">
18+
<Button key={label} active={active} data-test-id={dataTestId} disabled={this.props.disabled} onClick={this.props.onClick.bind(this, label)} bsSize="xsmall">
1819
{label}
1920
</Button>
2021
);
@@ -44,7 +45,8 @@ ViewSwitcher.propTypes = {
4445
buttonLabels: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
4546
activeButton: React.PropTypes.string,
4647
disabled: React.PropTypes.bool,
47-
onClick: () => {}
48+
dataTestId: React.PropTypes.string,
49+
onClick: React.PropTypes.func
4850
};
4951

5052
ViewSwitcher.displayName = 'ViewSwitcher';

src/internal-packages/explain/lib/components/summary-index-stat.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class SummaryIndexStat extends React.Component {
5656

5757
renderIndexDefinition() {
5858
if (this.props.index) {
59-
return <this.indexComponent index={this.props.index} />;
59+
return <this.indexComponent index={this.props.index} dataTestId={this.props.dataTestId}/>;
6060
}
6161
return null;
6262
}
@@ -88,7 +88,8 @@ SummaryIndexStat.propTypes = {
8888
dataLink: React.PropTypes.string, // info sprinkle (optional)
8989
indexType: React.PropTypes.oneOf(['MULTIPLE', 'UNAVAILABLE', 'COLLSCAN',
9090
'COVERED', 'INDEX']).isRequired,
91-
index: React.PropTypes.object
91+
index: React.PropTypes.object,
92+
dataTestId: React.PropTypes.string
9293
};
9394

9495
SummaryIndexStat.displayName = 'SummaryIndexStat';

src/internal-packages/explain/lib/stores/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ const CompassExplainStore = Reflux.createStore({
151151
// const filter = QueryStore.state.query;
152152
const options = {
153153
sort: this.sort,
154-
project: this.project,
154+
fields: this.project,
155155
skip: this.skip,
156156
limit: this.limit
157157
};

src/internal-packages/indexes/lib/component/index-definition-type.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class IndexDefinitionType extends React.Component {
6363
});
6464
return (
6565
<div className="index-definition-type">
66-
<p className="definition">
66+
<p className="definition" data-test-id={this.props.dataTestId}>
6767
{fields}
6868
</p>
6969
</div>
@@ -74,7 +74,8 @@ class IndexDefinitionType extends React.Component {
7474
IndexDefinitionType.displayName = 'IndexDefinitionType';
7575

7676
IndexDefinitionType.propTypes = {
77-
index: React.PropTypes.object.isRequired
77+
index: React.PropTypes.object.isRequired,
78+
dataTestId: React.PropTypes.string
7879
};
7980

8081
module.exports = IndexDefinitionType;

test/functional/querybar-functional.test.js

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,25 @@ describe('Compass Functional Tests for QueryBar #spectron', function() {
8181
'MongoDB Compass - localhost:27018/mongodb.fanclub'
8282
);
8383
});
84+
85+
it('goes to the index tab and creates an index', function() {
86+
return client
87+
.clickIndexesTab()
88+
.clickCreateIndexButton()
89+
.waitForCreateIndexModal()
90+
.inputCreateIndexDetails({name: 'age_1', field: 'age', type: '1 (asc)' })
91+
.clickCreateIndexModalButton()
92+
.waitForIndexCreation('age_1')
93+
.waitForVisibleInCompass('create-index-modal', true)
94+
.getIndexNames()
95+
.should.eventually.include('age_1');
96+
});
8497
});
8598

8699
context('when applying queries from the schema tab', function() {
87100
it('shows the sampling message', function() {
88101
return client
102+
.clickSchemaTab()
89103
.getSamplingMessageFromSchemaTab()
90104
.should.eventually.include('Query returned 100 documents.');
91105
});
@@ -126,6 +140,7 @@ describe('Compass Functional Tests for QueryBar #spectron', function() {
126140
it('goes to the documents tab', function() {
127141
return client
128142
.clickResetFilterButtonFromSchemaTab()
143+
.waitForStatusBar()
129144
.clickDocumentsTab()
130145
.getSamplingMessageFromDocumentsTab()
131146
.should.eventually.include('Query returned 100 documents. Displaying documents 1-20');
@@ -163,7 +178,6 @@ describe('Compass Functional Tests for QueryBar #spectron', function() {
163178
})
164179
.should.eventually.deep.equal(['_id', 'member_id', 'name']);
165180
});
166-
167181
it('disables editing mode for documents', function() {
168182
return client
169183
.getDocumentReadonlyStatus(1)
@@ -183,6 +197,65 @@ describe('Compass Functional Tests for QueryBar #spectron', function() {
183197
});
184198
});
185199
});
200+
201+
context('when applying queries to the explain tab', function() {
202+
it('goes to the explain plan tab', function() {
203+
return client
204+
.clickResetFilterButtonFromDocumentsTab()
205+
.waitForStatusBar()
206+
.clickExplainPlanTab()
207+
.getExplainPlanStatusMessage()
208+
.should.eventually.include('please enter your query first before applying and viewing your explain plan.');
209+
});
210+
211+
context('when applying a projection', function() {
212+
it('includes projection in the winning plan', function() {
213+
return client
214+
.waitForStatusBar()
215+
.inputProjectFromExplainPlanTab('{age: 1}')
216+
.clickApplyFilterButtonFromExplainPlanTab()
217+
.waitForStatusBar()
218+
.clickExplainViewDetails('raw-json')
219+
.waitForStatusBar()
220+
.getExplainRawJSONDocument()
221+
.should.eventually.include('PROJECTION');
222+
});
223+
});
224+
225+
context('when applying a sort', function() {
226+
it('includes fetch in the winning plan', function() {
227+
return client
228+
.waitForStatusBar()
229+
.inputSortFromExplainPlanTab('{name: 1}')
230+
.clickApplyFilterButtonFromExplainPlanTab()
231+
.waitForStatusBar()
232+
.clickExplainViewDetails('raw-json')
233+
.waitForStatusBar()
234+
.getExplainRawJSONDocument()
235+
.should.eventually.include('SORT');
236+
});
237+
238+
it('reduces the number of documents returned', function() {
239+
return client
240+
.inputSkipFromExplainPlanTab('10')
241+
.clickApplyFilterButtonFromExplainPlanTab()
242+
.waitForStatusBar()
243+
.getExplainDocumentsReturned()
244+
.should.eventually.equal('90');
245+
});
246+
});
247+
248+
context('when applying a limit', function() {
249+
it('only returns the number of documents specified by limit', function() {
250+
return client
251+
.inputLimitFromExplainPlanTab(5)
252+
.clickApplyFilterButtonFromExplainPlanTab()
253+
.waitForStatusBar()
254+
.getExplainDocumentsReturned()
255+
.should.eventually.equal('5');
256+
});
257+
});
258+
});
186259
});
187260
});
188261
});

test/functional/support/spectron-support.js

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -75,20 +75,21 @@ function isTimeoutError(e) {
7575
* fibonacci.
7676
*
7777
* @param {Function} fn - The function to use for waiting.
78-
* @param {String} selector - The selector for the element.
78+
* @param {String} sel - The selector for the element.
7979
* @param {Boolean} reverse - Whether to revers the conditions.
8080
* @param {Number} index - The timeout index to use from TIMEOUTS.
81+
*
82+
* @return {Promise} chainable promise
8183
*/
82-
function progressiveWait(fn, selector, reverse, index) {
84+
function progressiveWait(fn, sel, reverse, index) {
8385
const timeout = TIMEOUTS[index];
8486
debug(`Looking for element ${selector} with timeout ${timeout}ms`);
85-
return fn(selector, timeout, reverse)
87+
return fn(sel, timeout, reverse)
8688
.catch(function(e) {
8789
if (isTimeoutError(e) && timeout !== 13000) {
88-
return progressiveWait(fn, selector, reverse || false, index + 1);
89-
} else {
90-
throw e;
90+
return progressiveWait(fn, sel, reverse || false, index + 1);
9191
}
92+
throw e;
9293
});
9394
}
9495

@@ -98,6 +99,8 @@ function progressiveWait(fn, selector, reverse, index) {
9899
* @param {Function} waitUntil - The waitUntil function.
99100
* @param {Function} fn - The function to execute.
100101
* @param {Number} index - The timeout index.
102+
*
103+
* @return {Promise} chainable promise
101104
*/
102105
function progressiveWaitUntil(waitUntil, fn, index) {
103106
const timeout = TIMEOUTS[index];
@@ -106,35 +109,35 @@ function progressiveWaitUntil(waitUntil, fn, index) {
106109
.catch(function(e) {
107110
if (isTimeoutError(e) && timeout !== 13000) {
108111
return progressiveWaitUntil(waitUntil, fn, index + 1);
109-
} else {
110-
throw e;
111112
}
113+
throw e;
112114
});
113115
}
114116

115117
/**
116118
* Add the extended wait commands for Compass.
119+
*
120+
* @param {Object} client the spectron client to extend
117121
*/
118122
function addExtendedWaitCommands(client) {
119-
120123
/**
121124
* Wait for an element to exist in the Compass test suite.
122125
*
123-
* @param {String} selector - The CSS selector for the element.
126+
* @param {String} sel - The CSS selector for the element.
124127
* @param {Boolean} reverse - Whether to reverse the wait.
125128
*/
126-
client.addCommand('waitForExistInCompass', function(selector, reverse) {
127-
return progressiveWait(this.waitForExist.bind(this), selector, reverse, 0);
129+
client.addCommand('waitForExistInCompass', function(sel, reverse) {
130+
return progressiveWait(this.waitForExist.bind(this), sel, reverse, 0);
128131
});
129132

130133
/**
131134
* Wait for an element to be visible in the Compass test suite.
132135
*
133-
* @param {String} selector - The CSS selector for the element.
136+
* @param {String} sel - The CSS selector for the element.
134137
* @param {Boolean} reverse - Whether to reverse the wait.
135138
*/
136-
client.addCommand('waitForVisibleInCompass', function(selector, reverse) {
137-
return progressiveWait(this.waitForVisible.bind(this), selector, reverse, 0);
139+
client.addCommand('waitForVisibleInCompass', function(sel, reverse) {
140+
return progressiveWait(this.waitForVisible.bind(this), sel, reverse, 0);
138141
});
139142

140143
/**
@@ -154,7 +157,6 @@ function addExtendedWaitCommands(client) {
154157
* @param {Client} client - The client.
155158
*/
156159
function addWaitCommands(client) {
157-
158160
/**
159161
* Wait for document deletion to finish.
160162
*
@@ -788,7 +790,6 @@ function addKeyPressCommands(client) {
788790
* @param {Client} client - The client.
789791
*/
790792
function addGetCommands(client) {
791-
792793
/**
793794
* Get the slow operations list.
794795
*/
@@ -1169,7 +1170,6 @@ function addGetCommands(client) {
11691170
* @param {Client} client - The client.
11701171
*/
11711172
function addInputCommands(client) {
1172-
11731173
/**
11741174
* Enter the database name to drop.
11751175
*
@@ -1226,7 +1226,7 @@ function addInputCommands(client) {
12261226
const field = selector('create-index-modal-name');
12271227
return that
12281228
.waitForVisibleInCompass(field)
1229-
.setValue(field, model.name)
1229+
.setValue(field, model.name);
12301230
});
12311231
}
12321232
if (model.field) {
@@ -1294,6 +1294,70 @@ function addInputCommands(client) {
12941294
return this.setValue(input, filter);
12951295
});
12961296

1297+
/**
1298+
* Inputs a sort into the query bar from the explain plan tab.
1299+
*
1300+
* @param {String} filter - The filter.
1301+
*/
1302+
client.addCommand('inputSortFromExplainPlanTab', function(filter) {
1303+
const base = selector('explain-plan-content');
1304+
const input = `${base} .input-sort`;
1305+
return this.setValue(input, filter);
1306+
});
1307+
1308+
/**
1309+
* Inputs a projection into the query bar from the explain plan tab.
1310+
*
1311+
* @param {String} filter - The filter.
1312+
*/
1313+
client.addCommand('inputProjectFromExplainPlanTab', function(filter) {
1314+
const base = selector('explain-plan-content');
1315+
const input = `${base} .input-project`;
1316+
return this.setValue(input, filter);
1317+
});
1318+
1319+
/**
1320+
* Inputs a skip into the query bar from the documents tab.
1321+
*
1322+
* @param {String} filter - The filter.
1323+
*/
1324+
client.addCommand('inputSkipFromExplainPlanTab', function(filter) {
1325+
const base = selector('explain-plan-content');
1326+
const input = `${base} .input-skip`;
1327+
return this.setValue(input, filter);
1328+
});
1329+
1330+
/**
1331+
* Inputs a limit into the query bar from the documents tab.
1332+
*
1333+
* @param {String} filter - The filter.
1334+
*/
1335+
client.addCommand('inputLimitFromExplainPlanTab', function(filter) {
1336+
const base = selector('explain-plan-content');
1337+
const input = `${base} .input-limit`;
1338+
return this.setValue(input, filter);
1339+
});
1340+
1341+
/**
1342+
* Click one of the view details as buttons
1343+
*
1344+
* @param {String} view - the value should be either 'visual-tree' or 'raw-json'
1345+
*/
1346+
client.addCommand('clickExplainViewDetails', function(view) {
1347+
const button = selector('explain-view-' + view);
1348+
return this.click(button);
1349+
});
1350+
1351+
/**
1352+
* Get the explain plan raw json object
1353+
*/
1354+
client.addCommand('getExplainRawJSONDocument', function() {
1355+
const base = `${selector('readonly-document')} .element-value-is-string`;
1356+
return this.waitForVisibleInCompass(base).getText(base).then((values) => {
1357+
return values.map((str) => str.replace(/"/g, ''));
1358+
});
1359+
});
1360+
12971361
/**
12981362
* Input a projection into the query from the schema tab.
12991363
*

0 commit comments

Comments
 (0)