Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show event context #9198

Merged
merged 118 commits into from
Feb 22, 2017
Merged
Show file tree
Hide file tree
Changes from 65 commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
f80a169
Add app to display discover entry context
weltenwort Nov 18, 2016
05aedd8
Extract multi-transclude workaround into directive
weltenwort Nov 21, 2016
2e422f7
Add a generic local-navigation directive
weltenwort Nov 21, 2016
89b31e9
Use `<local-navigation>` for context size controls
weltenwort Nov 21, 2016
3227016
Wire up the context app state to the url
weltenwort Nov 21, 2016
ef78a3c
Add basic link from discover to the context app
weltenwort Nov 21, 2016
be355d8
Change context view breadcrumbs
weltenwort Nov 22, 2016
b2db703
Return promises from the reload and init actions
weltenwort Nov 22, 2016
c3d7459
Make the context size display editable
weltenwort Nov 22, 2016
9e5986c
Always sort on the index-pattern's time field
weltenwort Nov 23, 2016
d64f938
Improve doc and context link styling in docTable
weltenwort Nov 23, 2016
2acc123
Fix font-awesome class name typo
weltenwort Nov 23, 2016
4991bd8
Hide context link for non-time-based index patterns
weltenwort Nov 23, 2016
1bb189d
Merge branch 'master' into discover-context-app
weltenwort Dec 5, 2016
e10f458
Add setting to configure the default context size
weltenwort Dec 5, 2016
123d6b0
Add setting to configure the context size step
weltenwort Dec 5, 2016
dbaed4f
Enforce a minimal context size of 0
weltenwort Dec 5, 2016
840278f
Reimplement the local nav without multi-transclusion
weltenwort Dec 6, 2016
4730c77
Add visual indicator for first initialization
weltenwort Dec 6, 2016
2054287
Merge commit '10f7880' into discover-context-app
weltenwort Dec 13, 2016
a9c96aa
Improve loading ui and behaviour, refactor state
weltenwort Dec 13, 2016
a2b1456
Merge branch 'master' into discover-context-app
weltenwort Dec 13, 2016
bb1b056
Remove plus icons from "load more" buttons
weltenwort Dec 13, 2016
36c9b17
Merge branch 'master' into discover-context-app
weltenwort Dec 13, 2016
72e7faf
Merge branch 'master' into discover-context-app
weltenwort Dec 14, 2016
366962a
Fix AppState synchronization
weltenwort Dec 15, 2016
a98d967
Merge branch 'master' into discover-context-app
weltenwort Dec 15, 2016
778d3ce
Fix linting errors
weltenwort Dec 15, 2016
46d8472
Fix more linting errors
weltenwort Dec 15, 2016
3ae8a5d
Make anchor highlighting more prominent
weltenwort Dec 16, 2016
ddda2ff
Remove obsolete isInitialized from state
weltenwort Dec 16, 2016
84d0a3d
Replace history entry when persisting state
weltenwort Dec 16, 2016
491e212
Merge branch 'master' into discover-context-app
weltenwort Dec 16, 2016
89f3af2
Update to new kui css classes
weltenwort Dec 16, 2016
170b6e6
Merge branch 'master' into discover-context-app
weltenwort Dec 19, 2016
cfd0573
Change the design of the "load more" buttons
weltenwort Dec 19, 2016
3ed6941
Stop abusing the kuiLocalMenu styles for sizePicker
weltenwort Dec 19, 2016
d15b35c
Fix imports of removed style file
weltenwort Dec 19, 2016
c2d2240
Use more popular redux terminology
weltenwort Dec 20, 2016
ed354e0
Split state management into separate files
weltenwort Dec 20, 2016
62f7342
Add helpers to bind selectors to state
weltenwort Dec 20, 2016
5472730
Factor out the size picker into a separate directive
weltenwort Dec 20, 2016
b7a789d
Make "load more" buttons full-width
weltenwort Dec 20, 2016
34abd9c
Merge branch 'master' into discover-context-app
weltenwort Jan 2, 2017
e9cdb7a
Use tabs in the LocalNav
weltenwort Jan 2, 2017
00d1bbe
Use yellow outline for anchor highlighting
weltenwort Jan 2, 2017
d3a37cb
Use kui classes for links in discover detail links
weltenwort Jan 2, 2017
d4daca4
Merge branch 'master' into discover-context-app
weltenwort Jan 5, 2017
6ac96e2
Merge branch 'master' into discover-context-app
weltenwort Jan 6, 2017
2cbbf62
Add implementation notes
weltenwort Jan 6, 2017
ce2d015
Remove superfluous directives
weltenwort Jan 6, 2017
a7d32ac
Factor out loading button component
weltenwort Jan 6, 2017
fbb61c1
Add directory structure to notes
weltenwort Jan 6, 2017
82f88c4
Add unit tests
weltenwort Jan 10, 2017
2f31979
Use chai instead of expect.js in tests
weltenwort Jan 11, 2017
5cc91aa
Add more unit tests
weltenwort Jan 11, 2017
b80be8b
Merge branch 'master' into discover-context-app
weltenwort Jan 11, 2017
02440e8
Remove redux concepts in response to code review
weltenwort Jan 12, 2017
cb4d95f
Improve Context Log highlighting and UI. (#1)
cjcenizal Jan 12, 2017
2e5b192
Add missing successor loading status
weltenwort Jan 12, 2017
5c50c1d
Factor out initial queryParameters state creation
weltenwort Jan 16, 2017
43472f5
Add basic acceptance tests (WIP)
weltenwort Jan 17, 2017
5272a76
Add some context size tests
weltenwort Jan 18, 2017
2756db1
Remove selectors in reaction to review feedback
weltenwort Jan 18, 2017
de6983f
Bind SizePickerController to a proper name
weltenwort Jan 19, 2017
cbe3602
Merge branch 'master' into discover-context-app
weltenwort Jan 20, 2017
29e1fa8
Replace object spread syntax with Object.assign
weltenwort Jan 20, 2017
19df62f
Rename setCount to onChangeCount
weltenwort Jan 20, 2017
9e4dcea
Use Promise.try to catch more errors
weltenwort Jan 20, 2017
e0046e2
Track loading errors and notify the user about them
weltenwort Jan 20, 2017
1b7fb54
Disable size picker inputs until anchor is loaded
weltenwort Jan 20, 2017
cdae37c
Fix ContextPage class name
weltenwort Jan 23, 2017
9d411ea
Explicitly reset the view value in debounced input
weltenwort Jan 24, 2017
826910e
Replace uses of chai with expect.js
weltenwort Jan 25, 2017
0566459
Add docstrings and annotations
weltenwort Jan 25, 2017
4b275b9
Remove the need to separately pass the anchor uid
weltenwort Jan 25, 2017
61d7683
Re-use `KbnUrl` to encode the url components
weltenwort Jan 26, 2017
c0021f2
Preserve previous discover state in breadcrumb url
weltenwort Jan 26, 2017
a916f49
Start to remove duplication from PageObjects
weltenwort Jan 26, 2017
f07926b
Merge branch 'master' into discover-context-app
weltenwort Jan 27, 2017
05cc080
Merge branch 'master' into discover-context-app
weltenwort Jan 31, 2017
ef3573e
Update implementation notes
weltenwort Jan 31, 2017
183991e
Introduce loading status constants, separate state
weltenwort Feb 1, 2017
8997be7
Remove obsolete border work-around
weltenwort Feb 1, 2017
61f0423
Split query and query_parameters files
weltenwort Feb 1, 2017
6af8043
Extract route template into separate html file
weltenwort Feb 1, 2017
d8a7a4f
Replace _.spread with argument spread syntax
weltenwort Feb 2, 2017
a0040cc
Use new kui error info panel
weltenwort Feb 2, 2017
d23f2e4
Inline reversing the results
weltenwort Feb 2, 2017
e8862c7
Add a link to Discover to the error message
weltenwort Feb 2, 2017
0102c18
Rename disabled property of the loading button
weltenwort Feb 2, 2017
9ef0b69
Merge branch 'master' into discover-context-app
weltenwort Feb 3, 2017
9af7cb6
Show end-of-data warnings to the user
weltenwort Feb 3, 2017
f8dcd35
Rename query to queryBody where appropriate
weltenwort Feb 6, 2017
41ea1bf
Remove Discover breadcrumb
weltenwort Feb 6, 2017
0a4fa98
Merge branch 'master' into discover-context-app
weltenwort Feb 8, 2017
a3572d0
Get the config options lazily
weltenwort Feb 8, 2017
ce4c07f
Limit the context size to Elasticsearch's limit
weltenwort Feb 9, 2017
938e841
Increase the size picker width
weltenwort Feb 9, 2017
06245b9
Move the size picker to the left
weltenwort Feb 9, 2017
44c1e8c
Add comment about ngModel getter/setter
weltenwort Feb 10, 2017
519a3a4
Don't disable load-more buttons on failure
weltenwort Feb 10, 2017
3856851
Revert button styling of links in discover
weltenwort Feb 10, 2017
61be5c1
Adjust header content
weltenwort Feb 10, 2017
912df52
Merge branch 'master' into discover-context-app
weltenwort Feb 10, 2017
21c9098
Fix function names in unit tests
weltenwort Feb 10, 2017
c6ac75b
Add test for navigation from Discover to Context View
weltenwort Feb 14, 2017
9706068
Move updating the configuration into before block
weltenwort Feb 14, 2017
9b8bf37
Add query_parameter action tests
weltenwort Feb 14, 2017
8998279
Merge branch 'master' into discover-context-app
weltenwort Feb 15, 2017
ad1c856
Switch loading button and size picker locations
weltenwort Feb 16, 2017
d8f99e7
Rename entries to documents
weltenwort Feb 16, 2017
8a5b60e
Display loading step size on button
weltenwort Feb 16, 2017
50205b8
Merge branch 'master' into discover-context-app
weltenwort Feb 16, 2017
cb2bae6
Fix anchor highlight render bug in chrome
weltenwort Feb 16, 2017
12ce61b
Improve test case wording
weltenwort Feb 21, 2017
2784b0c
Merge branch 'master' into discover-context-app
weltenwort Feb 22, 2017
62bf542
Update notes to match the dir structure
weltenwort Feb 22, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/management/advanced-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ Markdown.
`notifications:lifetime:error`:: Specifies the duration in milliseconds for error notification displays. The default value is 300000. Set this field to `Infinity` to disable error notifications.
`notifications:lifetime:warning`:: Specifies the duration in milliseconds for warning notification displays. The default value is 10000. Set this field to `Infinity` to disable warning notifications.
`notifications:lifetime:info`:: Specifies the duration in milliseconds for information notification displays. The default value is 5000. Set this field to `Infinity` to disable information notifications.
`context:defaultSize`:: Specifies the initial number of surrounding entries to display in the context view. The default value is 5.
`context:step`:: Specifies the number to increment or decrement the context size by when using the buttons in the context view. The default value is 5.
127 changes: 127 additions & 0 deletions src/core_plugins/kibana/public/context/NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Discover Context App Implementation Notes
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file needs to be updated to reflect the current principles and directory structure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I will remove it, since most of what it describes is no longer true.


The implementation of this app is intended to exhibit certain desirable
properties by adhering to a set of *principles*. This document aims to explain
those and the *concepts* employed to achieve that.


## Principles

**Single Source of Truth**: A good user experience depends on the UI displaying
consistent information across the whole page. To achieve this, there should
always be a single source of truth for the application's state. In this
application this is the `ContextAppController::state` object.

**Unidirectional Data Flow**: While a single state promotes rendering
consistency, it does little to make the state changes easier to reason about.
To avoid having state mutations scattered all over the code, this app
implements a unidirectional data flow architecture. That means that the state
is treated as immutable throughout the application and a new state may only be
derived via the root reducer (see below).

**Unit-Testability**: Creating unit tests for large parts of the UI code is
made easy by expressing the state management logic mostly as side-effect-free
functions. The only place where side-effects are allowed are action creators
and the reducer middlewares. Due to the nature of AngularJS a certain amount of
impure code must be employed in some cases, e.g. when dealing with the isolate
scope bindings in `ContextAppController`.

**Loose Coupling**: An attempt was made to couple the parts that make up this
app as loosely as possible. This means using pure functions whenever possible
and isolating the angular directives diligently. To that end, the app has been
implemented as the independent `ContextApp` directive in [app.js](./app.js). It
does not access the Kibana `AppState` directly but communicates only via its
directive properties. The binding of these attributes to the state and thereby
to the route is performed by the `CreateAppRouteController`in
[index.js](./index.js). Similarly, the `SizePicker` directive only communicates
with its parent via the passed properties.


## Concepts

To adhere to the principles mentioned above, this app borrows some concepts
from the redux architecture that forms a ciruclar unidirectional data flow:

```

|* create initial state
v
+->+
| v
| |* state
| v
| |* selectors calculate derived (memoized) values
| v
| |* angular templates render values
| v
| |* angular dispatches actions in response to user action/system events
| v
| |* middleware processes the action
| v
| |* reducer derives new state from action
| v
+--+

```

**State**: The state is the single source of truth at
`ContextAppController::state` and may only be replaced by a new state create
via the root reducer.

**Reducer**: The reducer at `ContextAppController.reducer` can derive a new
state from the previous state and an action. It must be a pure function and can
be composed from sub-reducers using various utilities like
`createReducerPipeline`, that passes the action and the previous sub-reducer's
result to each sub-reducer in order. The reducer is only called by the dispatch
function located at `ContextAppController::dispatch`.

**Action**: Actions are objets that describe user or system actions in a
declarative manner. Each action is supposed to be passed to
`ContextAppController::dispatch`, that passes them to each of its middlewares
in turn before calling the root reducer with the final action. Usually, actions
are created using helper functions called action creators to ensure they adhere
to the [Flux Standard Action] schema.

[Flux Standard Action]: https://github.com/acdlite/flux-standard-action

**Selector**: To decouple the view from the specific state structure, selectors
encapsulate the knowledge about how to retrieve a particular set of values from
the state in a single location. Additionally, selectors can encapsulate the
logic for calculating derived properties from the state, which should be kept
as flat as possible and free of duplicate information. The performance penalty
for performing these calculations at query time is commonly circumvented by
memoizing the selector results as is the case with `createSelector` from
[redux_lite/selector_helpers.js](./redux_lite/selector_helpers.js).


## Directory Structure

**index.js**: Defines the route and renders the `<context-app>` directive,
binding it to the `AppState`.

**app.js**: Defines the `<context-app>` directive, that is at the root of the
application. Creates the store, reducer and bound actions/selectors.

**query.js**: Exports the actions, reducers and selectors related to the
query status and results.

**query_parameters.js**: Exports the actions, reducers and selectors related to
the parameters used to construct the query.

**components/loading_button**: Defines the `<context-loading-button>`
directive including its respective styles.

**components/size_picker**: Defines the `<context-size-picker>`
directive including its respective styles.

**api/anchor.js**: Exports `fetchAnchor()` that creates and executes the
query for the anchor document.

**api/context.js**: Exports `fetchPredecessors()` and `fetchSuccessors()` that
create and execute the queries for the preceeding and succeeding documents.

**api/utils**: Exports various functions used to create and transform
queries.

**redux_lite**: Exports various functions to create the dispatcher, action
creators, reducers and selectors.
84 changes: 84 additions & 0 deletions src/core_plugins/kibana/public/context/api/__tests__/anchor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { expect } from 'chai';
import sinon from 'sinon';

import { fetchAnchor } from 'plugins/kibana/context/api/anchor';


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI the AirBnB style guide advises against multiple consecutive blank lines (I can't find the rule but they note it in an issue comment: airbnb/javascript#1048 (comment)). I don't mind if you leave the whitespace the way it is now because the linter can fix it on it's own, but I just wanted to let you know. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been using the following rules:

  • Group third-party and local imports using single blank lines, sort alphabetically by filename within the groups.
  • Separate the import section from other code by two blank lines.
  • Separate the export section from other code by two blank lines.

This results in:

// third-party/global imports
// ...

// local imports
// ...


// code
// ...
// ...


// exports
// ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation.

describe('context app', function () {
describe('function fetchAnchor', function () {
it('should use the `search` api to query the given index', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub(['hit1']);

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
expect(esStub.search.calledOnce).to.be.true;
expect(esStub.search.firstCall.args)
.to.have.lengthOf(1)
.with.deep.property('[0].index', 'index1');
});
});

it('should include computed fields in the query', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub(['hit1']);

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(() => {
expect(esStub.search.calledOnce).to.be.true;
expect(esStub.search.firstCall.args)
.to.have.lengthOf(1)
.with.deep.property('[0].body')
.that.contains.all.keys(['script_fields', 'docvalue_fields', 'stored_fields']);
});
});

it('should reject with an error when no hits were found', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub([]);

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then(
() => {
expect(true, 'expected the promise to be rejected').to.be.false;
},
(error) => {
expect(error).to.be.an('error');
}
);
});

it('should return the first hit after adding an anchor marker', function () {
const indexPatternStub = createIndexPatternStub('index1');
const esStub = createEsStub([{ property1: 'value1' }, {}]);

return fetchAnchor(esStub, indexPatternStub, 'UID', { '@timestamp': 'desc' })
.then((anchorDocument) => {
expect(anchorDocument).to.have.property('property1', 'value1');
expect(anchorDocument).to.have.property('$$_isAnchor', true);
});
});
});
});


function createIndexPatternStub(indices) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we move these helpers to the top of the file? I find that structure to be more readable, and AirBnB's rules will cause the linter to complain because of no-use-before-define.

Copy link
Member Author

@weltenwort weltenwort Jan 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm used to applying the pattern suggested in Robert C. Martin's "Clean Code" in regards to ordering: It should be easy to read by starting with the highlights and getting more detailed the further the reader progresses through the file.

return {
getComputedFields: sinon.stub()
.returns({}),
toIndexList: sinon.stub()
.returns(indices),
};
}

function createEsStub(hits) {
return {
search: sinon.stub()
.returns({
hits: {
hits,
total: hits.length,
},
}),
};
}
27 changes: 27 additions & 0 deletions src/core_plugins/kibana/public/context/api/anchor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import _ from 'lodash';

import { addComputedFields } from './utils/fields';
import { createAnchorQuery } from './utils/queries';


async function fetchAnchor(es, indexPattern, uid, sort) {
const indices = await indexPattern.toIndexList();
const response = await es.search({
index: indices,
body: addComputedFields(indexPattern, createAnchorQuery(uid, sort)),
});

if (_.get(response, ['hits', 'total'], 0) < 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the ES client returns an error? We should handle that scenario gracefully. I usually display a user friendly error message with the notifier and log the actual error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but I will handle that on a level closer to the UI. This function is not concerned with user interaction.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would at least generally translate the ES errors into something more generic at the service level, so that clients of the service don't end up coupling themselves to the ES client.

throw new Error('Failed to load anchor row.');
}

return {
...response.hits.hits[0],
$$_isAnchor: true,
};
}


export {
fetchAnchor,
};
53 changes: 53 additions & 0 deletions src/core_plugins/kibana/public/context/api/context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import _ from 'lodash';

import { addComputedFields } from './utils/fields';
import { getDocumentUid } from './utils/ids';
import { createSuccessorsQuery } from './utils/queries.js';
import { reverseQuerySort } from './utils/sorting';


async function fetchSuccessors(es, indexPattern, anchorDocument, sort, size) {
const successorsQuery = prepareQuery(indexPattern, anchorDocument, sort, size);
const results = await performQuery(es, indexPattern, successorsQuery);
return results;
}

async function fetchPredecessors(es, indexPattern, anchorDocument, sort, size) {
const successorsQuery = prepareQuery(indexPattern, anchorDocument, sort, size);
const predecessorsQuery = reverseQuerySort(successorsQuery);
const reversedResults = await performQuery(es, indexPattern, predecessorsQuery);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reversedResults doesn't have much meaning for me... can we rename this to describe it more clearly, e.g. resultsAscending or resultsDescending? Better yet, something that reflects what they're sorted on, e.g. resultsAscendingByDate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point there is no knowledge about how it is sorted, just that it is reversed from what it was before. I'll try to think of a name that makes that more obvious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sequence of operations is actually quite simple:

  1. create the query successorsQuery
  2. reverse the query (because we only have search_after and no search_before in Elasticsearch), creating the predecessorsQuery
  3. execute the predecessorsQuery, getting back the results in reversed order, ergo reversedResults
  4. reverse the results to get correctly ordered results

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, sounds good! I think this makes more sense to me now and we can ignore my original comment. Thanks for the explanation.

const results = reverseResults(reversedResults);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's either just call reversedResults.reverse() directly here, or update reverseResults to return a copy of reversedResults without mutating it. As this code stands, I'm not sure what the intention was behind breaking out this logic into a separate function, since it's only used in one place and mutates the argument.

return results;
}


function prepareQuery(indexPattern, anchorDocument, sort, size) {
const anchorUid = getDocumentUid(anchorDocument._type, anchorDocument._id);
const successorsQuery = addComputedFields(
indexPattern,
createSuccessorsQuery(anchorUid, anchorDocument.sort, sort, size)
);
return successorsQuery;
Copy link
Contributor

@cjcenizal cjcenizal Jan 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a comment about renaming addComputedFields to createQueryBody. If we make that change then I'd suggest that everywhere we call it, we rename the variable to which we assign its return value in a way that reflects the new name, e.g.:

const successorsQueryBody = createQueryBody(
  indexPattern,
  createSuccessorsQuery(anchorDocument.sort, sort, size)
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think we can make this code more readable by naming the function to reflect the returned value more clearly, and by breaking out arguments into named variables:

function createSuccessorsQueryBody(indexPattern, anchorDocument, sort, size) {
  const rawQueryBody = createSuccessorsQuery(anchorDocument.sort, sort, size);
  const successorsQueryBody = createQueryBody(indexPattern, rawQueryBody);
  return successorsQueryBody;
}

Copy link
Member Author

@weltenwort weltenwort Jan 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think addComputedFields represents exactly what the function does. It does not create a query body, but extends a preexisting one with computed fields. createSuccessorQuery creates it, which also matches its name. If you prefer QueryBody instead of Query, I could live with that. I just made something up because I could not find consistent naming.

Copy link
Contributor

@cjcenizal cjcenizal Feb 2, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some more thought, I think that most of my comments about renaming and organization don't need to block this PR and can be addressed in a later refactoring PR, after I've had some time to put together a more comprehensive proposal. 😄

}

async function performQuery(es, indexPattern, query) {
const indices = await indexPattern.toIndexList();

const response = await es.search({
index: indices,
body: query,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as in anchor.js: we should catch and handle errors from the ES client here.


return _.get(response, ['hits', 'hits'], []);
}

function reverseResults(results) {
results.reverse();
return results;
}


export {
fetchPredecessors,
fetchSuccessors,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect } from 'chai';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you mind switching from Chai to expect.js? The vast majority of our tests are written with expect.js. It looks like Chai was pulled in with Timelion which was originally an external plugin. We have an open issue about moving to Chai but until then it would be better to stick to expect.js.


import { addComputedFields } from 'plugins/kibana/context/api/utils/fields';


describe('context app', function () {
describe('function addComputedFields', function () {
it('should add the `script_fields` property defined in the given index pattern', function () {
const getComputedFields = () => ({
scriptFields: {
sourcefield1: {
script: '_source.field1',
},
}
});

expect(addComputedFields({ getComputedFields }, {})).to.have.property('script_fields')
.that.is.deep.equal(getComputedFields().scriptFields);
});

it('should add the `docvalue_fields` property defined in the given index pattern', function () {
const getComputedFields = () => ({
docvalueFields: ['field1'],
});

expect(addComputedFields({ getComputedFields }, {})).to.have.property('docvalue_fields')
.that.is.deep.equal(getComputedFields().docvalueFields);
});

it('should add the `stored_fields` property defined in the given index pattern', function () {
const getComputedFields = () => ({
storedFields: ['field1'],
});

expect(addComputedFields({ getComputedFields }, {})).to.have.property('stored_fields')
.that.is.deep.equal(getComputedFields().storedFields);
});

it('should preserve other properties of the query', function () {
const getComputedFields = () => ({});

expect(addComputedFields({ getComputedFields }, { property1: 'value1' }))
.to.have.property('property1', 'value1');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { expect } from 'chai';

import {
createAnchorQuery,
createSuccessorsQuery,
} from 'plugins/kibana/context/api/utils/queries';


describe('context app', function () {
describe('function createAnchorQuery', function () {
it('should return a search definition that searches the given uid', function () {
expect(createAnchorQuery('UID', { '@timestamp': 'desc' }))
.to.have.deep.property('query.terms._uid[0]', 'UID');
});

it('should return a search definition that sorts by the given criteria', function () {
expect(createAnchorQuery('UID', { '@timestamp': 'desc' }))
.to.have.deep.property('sort[0]')
.that.is.deep.equal({ '@timestamp': 'desc' });
});
});

describe('function createSuccessorsQuery', function () {
it('should return a search definition that includes the given size', function () {
expect(createSuccessorsQuery('UID', [0], { '@timestamp' : 'desc' }, 10))
.to.have.property('size', 10);
});

it('should return a search definition that sorts by the given criteria and uid', function () {
expect(createSuccessorsQuery('UID', [0], { '@timestamp' : 'desc' }, 10))
.to.have.property('sort')
.that.is.deep.equal([
{ '@timestamp': 'desc' },
{ _uid: 'asc' },
]);
});

it('should return a search definition that search after the given uid', function () {
expect(createSuccessorsQuery('UID', [0], { '@timestamp' : 'desc' }, 10))
.to.have.property('search_after')
.that.is.deep.equal([0, 'UID']);
});
});
});
Loading