-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
React/EUI-ify indexed fields table #16695
Changes from 15 commits
45afdf7
20fe3b8
3109f61
ef63d0a
6a43eb8
cd2a2c1
22d18d8
35021ba
898f99b
adb450b
ecd1ec0
a055a3a
c2d58bd
979acef
c5633aa
169e31f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
import _ from 'lodash'; | ||
import './index_header'; | ||
import './indexed_fields_table'; | ||
import './scripted_field_editor'; | ||
import './source_filters_table'; | ||
import { KbnUrlProvider } from 'ui/url'; | ||
|
@@ -9,11 +8,14 @@ import { fatalError } from 'ui/notify'; | |
import uiRoutes from 'ui/routes'; | ||
import { uiModules } from 'ui/modules'; | ||
import template from './edit_index_pattern.html'; | ||
import { FieldWildcardProvider } from 'ui/field_wildcard'; | ||
|
||
import React from 'react'; | ||
import { render, unmountComponentAtNode } from 'react-dom'; | ||
import { IndexedFieldsTable } from './indexed_fields_table'; | ||
import { ScriptedFieldsTable } from './scripted_fields_table'; | ||
|
||
const REACT_INDEXED_FIELDS_DOM_ELEMENT_ID = 'reactIndexedFieldsTable'; | ||
const REACT_SCRIPTED_FIELDS_DOM_ELEMENT_ID = 'reactScriptedFieldsTable'; | ||
|
||
function updateScriptedFieldsTable($scope, $state) { | ||
|
@@ -54,6 +56,41 @@ function destroyScriptedFieldsTable() { | |
node && unmountComponentAtNode(node); | ||
} | ||
|
||
function updateIndexedFieldsTable($scope, $state) { | ||
if ($state.tab === 'indexedFields') { | ||
$scope.$$postDigest(() => { | ||
const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID); | ||
if (!node) { | ||
return; | ||
} | ||
|
||
render( | ||
<IndexedFieldsTable | ||
fields={$scope.fields} | ||
indexPattern={$scope.indexPattern} | ||
fieldFilter={$scope.fieldFilter} | ||
fieldWildcardMatcher={$scope.fieldWildcardMatcher} | ||
indexedFieldTypeFilter={$scope.indexedFieldTypeFilter} | ||
helpers={{ | ||
redirectToRoute: (obj, route) => { | ||
$scope.kbnUrl.redirectToRoute(obj, route); | ||
$scope.$apply(); | ||
}, | ||
}} | ||
/>, | ||
node, | ||
); | ||
}); | ||
} else { | ||
destroyIndexedFieldsTable(); | ||
} | ||
} | ||
|
||
function destroyIndexedFieldsTable() { | ||
const node = document.getElementById(REACT_INDEXED_FIELDS_DOM_ELEMENT_ID); | ||
node && unmountComponentAtNode(node); | ||
} | ||
|
||
uiRoutes | ||
.when('/management/kibana/indices/:indexPatternId', { | ||
template, | ||
|
@@ -87,7 +124,9 @@ uiModules.get('apps/management') | |
$scope, $location, $route, config, courier, Notifier, Private, AppState, docTitle, confirmModal) { | ||
const notify = new Notifier(); | ||
const $state = $scope.state = new AppState(); | ||
const { fieldWildcardMatcher } = Private(FieldWildcardProvider); | ||
|
||
$scope.fieldWildcardMatcher = fieldWildcardMatcher; | ||
$scope.editSectionsProvider = Private(IndicesEditSectionsProvider); | ||
$scope.kbnUrl = Private(KbnUrlProvider); | ||
$scope.indexPattern = $route.current.locals.indexPattern; | ||
|
@@ -100,6 +139,9 @@ uiModules.get('apps/management') | |
$scope.$watch('indexPattern.fields', function () { | ||
$scope.editSections = $scope.editSectionsProvider($scope.indexPattern); | ||
$scope.refreshFilters(); | ||
$scope.fields = $scope.indexPattern.getNonScriptedFields(); | ||
updateIndexedFieldsTable($scope, $state); | ||
updateScriptedFieldsTable($scope, $state); | ||
}); | ||
|
||
$scope.refreshFilters = function () { | ||
|
@@ -123,6 +165,7 @@ uiModules.get('apps/management') | |
|
||
$scope.changeTab = function (obj) { | ||
$state.tab = obj.index; | ||
updateIndexedFieldsTable($scope, $state); | ||
updateScriptedFieldsTable($scope, $state); | ||
$state.save(); | ||
}; | ||
|
@@ -139,7 +182,10 @@ uiModules.get('apps/management') | |
$scope.refreshFields = function () { | ||
const confirmModalOptions = { | ||
confirmButtonText: 'Refresh', | ||
onConfirm: () => { $scope.indexPattern.refreshFields(); }, | ||
onConfirm: async () => { | ||
await $scope.indexPattern.refreshFields(); | ||
$scope.fields = $scope.indexPattern.getNonScriptedFields(); | ||
}, | ||
title: 'Refresh field list?' | ||
}; | ||
confirmModal( | ||
|
@@ -187,8 +233,21 @@ uiModules.get('apps/management') | |
}; | ||
|
||
$scope.$watch('fieldFilter', () => { | ||
if ($scope.fieldFilter !== undefined && $state.tab === 'scriptedFields') { | ||
updateScriptedFieldsTable($scope, $state); | ||
if ($scope.fieldFilter === undefined) { | ||
return; | ||
} | ||
|
||
switch($state.tab) { | ||
case 'indexedFields': | ||
updateIndexedFieldsTable($scope, $state); | ||
case 'scriptedFields': | ||
updateScriptedFieldsTable($scope, $state); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor suggestion: how about early exiting here to avoid nesting, and a slightly terser switch instead of else if? if ($scope.fieldFilter === undefined) {
return;
}
switch ($state.tab) {
case 'indexedFields':
updateIndexedFieldsTable($scope, $state);
case 'scriptedFields':
updateScriptedFieldsTable($scope, $state);
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updated 👍 |
||
}); | ||
|
||
$scope.$watch('indexedFieldTypeFilter', () => { | ||
if ($scope.indexedFieldTypeFilter !== undefined && $state.tab === 'indexedFields') { | ||
updateIndexedFieldsTable($scope, $state); | ||
} | ||
}); | ||
|
||
|
@@ -199,8 +258,7 @@ uiModules.get('apps/management') | |
}); | ||
|
||
$scope.$on('$destory', () => { | ||
destroyIndexedFieldsTable(); | ||
destroyScriptedFieldsTable(); | ||
}); | ||
|
||
updateScriptedFieldsTable($scope, $state); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`IndexedFieldsTable should filter based on the query bar 1`] = ` | ||
<div> | ||
<Table | ||
editField={[Function]} | ||
indexPattern={ | ||
Object { | ||
"getNonScriptedFields": [Function], | ||
} | ||
} | ||
items={ | ||
Array [ | ||
Object { | ||
"displayName": "Elastic", | ||
"excluded": false, | ||
"format": undefined, | ||
"indexPattern": undefined, | ||
"name": "Elastic", | ||
"routes": undefined, | ||
"searchable": true, | ||
}, | ||
] | ||
} | ||
/> | ||
</div> | ||
`; | ||
|
||
exports[`IndexedFieldsTable should filter based on the type filter 1`] = ` | ||
<div> | ||
<Table | ||
editField={[Function]} | ||
indexPattern={ | ||
Object { | ||
"getNonScriptedFields": [Function], | ||
} | ||
} | ||
items={ | ||
Array [ | ||
Object { | ||
"displayName": "timestamp", | ||
"excluded": false, | ||
"format": undefined, | ||
"indexPattern": undefined, | ||
"name": "timestamp", | ||
"routes": undefined, | ||
"type": "date", | ||
}, | ||
] | ||
} | ||
/> | ||
</div> | ||
`; | ||
|
||
exports[`IndexedFieldsTable should render normally 1`] = ` | ||
<div> | ||
<Table | ||
editField={[Function]} | ||
indexPattern={ | ||
Object { | ||
"getNonScriptedFields": [Function], | ||
} | ||
} | ||
items={ | ||
Array [ | ||
Object { | ||
"displayName": "Elastic", | ||
"excluded": false, | ||
"format": undefined, | ||
"indexPattern": undefined, | ||
"name": "Elastic", | ||
"routes": undefined, | ||
"searchable": true, | ||
}, | ||
Object { | ||
"displayName": "timestamp", | ||
"excluded": false, | ||
"format": undefined, | ||
"indexPattern": undefined, | ||
"name": "timestamp", | ||
"routes": undefined, | ||
"type": "date", | ||
}, | ||
Object { | ||
"displayName": "conflictingField", | ||
"excluded": false, | ||
"format": undefined, | ||
"indexPattern": undefined, | ||
"name": "conflictingField", | ||
"routes": undefined, | ||
"type": "conflict", | ||
}, | ||
] | ||
} | ||
/> | ||
</div> | ||
`; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import React from 'react'; | ||
import { shallow } from 'enzyme'; | ||
|
||
import { IndexedFieldsTable } from '../indexed_fields_table'; | ||
|
||
jest.mock('@elastic/eui', () => ({ | ||
EuiFlexGroup: 'eui-flex-group', | ||
EuiFlexItem: 'eui-flex-item', | ||
EuiIcon: 'eui-icon', | ||
EuiInMemoryTable: 'eui-in-memory-table', | ||
TooltipTrigger: 'tooltip-trigger' | ||
})); | ||
|
||
jest.mock('../components/table', () => ({ | ||
// Note: this seems to fix React complaining about non lowercase attributes | ||
Table: () => { | ||
return 'table'; | ||
} | ||
})); | ||
|
||
const helpers = { | ||
redirectToRoute: () => {}, | ||
}; | ||
|
||
const fields = [ | ||
{ name: 'Elastic', displayName: 'Elastic', searchable: true }, | ||
{ name: 'timestamp', displayName: 'timestamp', type: 'date' }, | ||
{ name: 'conflictingField', displayName: 'conflictingField', type: 'conflict' }, | ||
]; | ||
|
||
const indexPattern = { | ||
getNonScriptedFields: () => fields, | ||
}; | ||
|
||
describe('IndexedFieldsTable', () => { | ||
it('should render normally', async () => { | ||
const component = shallow( | ||
<IndexedFieldsTable | ||
fields={fields} | ||
indexPattern={indexPattern} | ||
helpers={helpers} | ||
fieldWildcardMatcher={() => {}} | ||
/> | ||
); | ||
|
||
// Allow the componentWillMount code to execute | ||
// https://github.com/airbnb/enzyme/issues/450 | ||
await component.update(); // Fire `componentWillMount()` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can simplify all of this from #16968
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so much better!! |
||
await component.update(); // Fire `componentWillMount()` | ||
await component.update(); // Force update the component post async actions | ||
|
||
expect(component).toMatchSnapshot(); | ||
}); | ||
|
||
it('should filter based on the query bar', async () => { | ||
const component = shallow( | ||
<IndexedFieldsTable | ||
fields={fields} | ||
indexPattern={indexPattern} | ||
helpers={helpers} | ||
fieldWildcardMatcher={() => {}} | ||
/> | ||
); | ||
|
||
// Allow the componentWillMount code to execute | ||
// https://github.com/airbnb/enzyme/issues/450 | ||
await component.update(); // Fire `componentWillMount()` | ||
await component.update(); // Force update the component post async actions | ||
|
||
component.setProps({ fieldFilter: 'Elast' }); | ||
component.update(); | ||
|
||
expect(component).toMatchSnapshot(); | ||
}); | ||
|
||
it('should filter based on the type filter', async () => { | ||
const component = shallow( | ||
<IndexedFieldsTable | ||
fields={fields} | ||
indexPattern={indexPattern} | ||
helpers={helpers} | ||
fieldWildcardMatcher={() => {}} | ||
/> | ||
); | ||
|
||
// Allow the componentWillMount code to execute | ||
// https://github.com/airbnb/enzyme/issues/450 | ||
await component.update(); // Fire `componentWillMount()` | ||
await component.update(); // Force update the component post async actions | ||
|
||
component.setProps({ indexedFieldTypeFilter: 'date' }); | ||
component.update(); | ||
|
||
expect(component).toMatchSnapshot(); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still seeing an issue where the fields aren't refreshing once I click
Refresh Fields
. I think it's because this change is never propagating to the React component.I'm wondering if we just let this parent container control the fields and pass it in so we can easily tie a change in the fields to the React lifecycle. Or maybe find another way.
But if you like the first idea, here is a some code I whipped up that might work:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, implemented similar changes