Skip to content

Commit b4bb35b

Browse files
author
guyplusplus
committed
New dropdown to select data
1 parent 768397c commit b4bb35b

File tree

4 files changed

+199
-2
lines changed

4 files changed

+199
-2
lines changed

vis_type_custom_form_filter_accounts/public/custom_form_filter_accounts_vis_controller.tsx

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,36 @@ import {
2424
EuiFormRow,
2525
EuiFieldText,
2626
EuiSpacer,
27+
EuiComboBox,
2728
} from '@elastic/eui';
2829
import { useKibana } from '../../../src/plugins/kibana_react/public';
2930
import { CustomFormFilterAccountsVisDependencies } from './plugin';
3031
import { CustomFormFilterAccountsVisParams } from './types';
3132
import { removeFiltersByControlledBy, stringToInt, stringToFloat } from './filter_helper';
33+
import { fetchData } from './fetch_data';
3234

3335
const filterControlledBy = 'accountsVis';
3436

3537
interface CustomFormFilterAccountsVisComponentProps extends CustomFormFilterAccountsVisParams {
3638
renderComplete: () => {};
3739
}
3840

41+
interface AccountFormState {
42+
age: string,
43+
minimumBalance: string,
44+
countryStates: any,
45+
countryStateSelected: any
46+
}
47+
3948
/**
4049
* The CustomFormFilterAccountsVisComponent renders the form.
4150
*/
4251
class CustomFormFilterAccountsVisComponent extends React.Component<CustomFormFilterAccountsVisComponentProps> {
4352

53+
asyncInitStated : boolean = false;
54+
isLoading : boolean = true;
55+
state : AccountFormState;
56+
4457
/**
4558
* Will be called after the first render when the component is present in the DOM.
4659
*
@@ -63,17 +76,46 @@ class CustomFormFilterAccountsVisComponent extends React.Component<CustomFormFil
6376

6477
constructor(props: CustomFormFilterAccountsVisComponentProps) {
6578
super(props);
79+
6680
removeFiltersByControlledBy(this.props.filterManager, filterControlledBy);
81+
const initialCountryStates = [{label: '-- All --', value: 0}];
6782
this.state = {
6883
age: "",
69-
minimumBalance: ""
84+
minimumBalance: "",
85+
countryStates: initialCountryStates,
86+
countryStateSelected: [initialCountryStates[0]],
7087
};
7188
if(props.age != null)
7289
this.state.age = String(props.age);
7390
if(props.minimumBalance != null)
7491
this.state.minimumBalance = String(props.minimumBalance);
7592
}
7693

94+
async initControls() {
95+
if(this.asyncInitStated)
96+
return;
97+
this.asyncInitStated = true;
98+
let statesArray = null;
99+
try {
100+
statesArray = await fetchData(this.props.coreSetup, "bank*", "state.keyword");
101+
statesArray.sort();
102+
}
103+
catch(error) {
104+
statesArray = null;
105+
}
106+
if(!statesArray)
107+
statesArray = [];
108+
statesArray.unshift('-- All --');
109+
let countryStates = [];
110+
for(let i = 0; i<statesArray.length; i++) {
111+
countryStates.push({label: statesArray[i], value: i});
112+
}
113+
this.isLoading = false;
114+
this.setState({
115+
["countryStates"]: countryStates
116+
});
117+
}
118+
77119
onClickButtonApplyFilter = () => {
78120
removeFiltersByControlledBy(this.props.filterManager, filterControlledBy);
79121

@@ -113,6 +155,23 @@ class CustomFormFilterAccountsVisComponent extends React.Component<CustomFormFil
113155
};
114156
this.props.filterManager.addFilters(minimumBalanceFilter);
115157
}
158+
159+
if(this.state.countryStateSelected[0].value != 0) {
160+
const stateFilter = {
161+
meta: {
162+
controlledBy: filterControlledBy,
163+
alias: 'State: ' + this.state.countryStateSelected[0].label,
164+
disabled: false,
165+
negate: false,
166+
},
167+
query: {
168+
match_phrase: {
169+
state: this.state.countryStateSelected[0].label
170+
}
171+
}
172+
};
173+
this.props.filterManager.addFilters(stateFilter);
174+
}
116175
}
117176

118177
onClickButtonDeleteFilter = () => {
@@ -122,6 +181,7 @@ class CustomFormFilterAccountsVisComponent extends React.Component<CustomFormFil
122181
onClickButtonClearForm = () => {
123182
this.state.age = "";
124183
this.state.minimumBalance = "";
184+
this.state.countryStateSelected = [this.state.countryStates[0]];
125185
removeFiltersByControlledBy(this.props.filterManager, filterControlledBy);
126186
this.forceUpdate();
127187
};
@@ -142,10 +202,17 @@ class CustomFormFilterAccountsVisComponent extends React.Component<CustomFormFil
142202
});
143203
};
144204

205+
onCountryStateChange = selectedOptions => {
206+
this.setState({
207+
["countryStateSelected"]: selectedOptions
208+
});
209+
};
210+
145211
/**
146212
* Render the actual HTML.
147213
*/
148214
render() {
215+
this.initControls();
149216
const minimumBalanceHelpText = `Input account minimum balance (Maximum is ${this.props.maximumBalance})`;
150217
return (
151218
<div className="cffVis" >
@@ -157,6 +224,16 @@ class CustomFormFilterAccountsVisComponent extends React.Component<CustomFormFil
157224
<EuiFieldText name="minimumBalance" onChange={e => this.onFormChange(e)} value={this.state.minimumBalance} />
158225
</EuiFormRow>
159226
<EuiSpacer />
227+
<EuiComboBox
228+
placeholder="Select a state"
229+
isLoading={this.isLoading}
230+
singleSelection={{ asPlainText: true }}
231+
options={this.state.countryStates}
232+
selectedOptions={this.state.countryStateSelected}
233+
onChange={this.onCountryStateChange}
234+
isClearable={false}
235+
/>
236+
<EuiSpacer />
160237
<EuiButton onClick={this.onClickButtonApplyFilter} fill>Apply filter</EuiButton>&nbsp;
161238
<EuiButton onClick={this.onClickButtonDeleteFilter} >Delete filter</EuiButton>&nbsp;
162239
<EuiButton onClick={this.onClickButtonClearForm} >Clear form</EuiButton>&nbsp;
@@ -192,6 +269,7 @@ export function CustomFormFilterAccountsVisWrapper(props: CustomFormFilterAccoun
192269
renderComplete={props.renderComplete}
193270
timefilter={kibana.services.timefilter}
194271
filterManager={kibana.services.filterManager}
272+
coreSetup={kibana.services.coreSetup}
195273
/>
196274
);
197275
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import {
2+
IFieldType,
3+
TimefilterContract,
4+
SearchSourceFields,
5+
DataPublicPluginStart,
6+
} from '../../../src/plugins/data/public';
7+
import { CoreSetup } from '../../../src/core/public';
8+
9+
10+
function getEscapedQuery(query = '') {
11+
// https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#_standard_operators
12+
return query.replace(/[.?+*|{}[\]()"\\#@&<>~]/g, (match) => `\\${match}`);
13+
}
14+
15+
interface TermsAggArgs {
16+
field?: IFieldType;
17+
size: number | null;
18+
direction: string;
19+
query?: string;
20+
}
21+
22+
const termsAgg = ({ field, size, direction, query }: TermsAggArgs) => {
23+
const terms: any = {
24+
order: {
25+
_count: direction,
26+
},
27+
};
28+
29+
if (size) {
30+
terms.size = size < 1 ? 1 : size;
31+
}
32+
33+
if (field?.scripted) {
34+
terms.script = {
35+
source: field.script,
36+
lang: field.lang,
37+
};
38+
terms.value_type = field.type === 'number' ? 'float' : field.type;
39+
} else {
40+
terms.field = field?.name;
41+
}
42+
43+
if (query) {
44+
terms.include = `.*${getEscapedQuery(query)}.*`;
45+
}
46+
47+
return {
48+
termsAgg: {
49+
terms,
50+
},
51+
};
52+
};
53+
54+
export async function fetchData(core: CoreSetup, indexName, fieldName) {
55+
const [, { data: dataPluginStart }] = await core.getStartServices();
56+
//search for indexes matching the name
57+
const indexPatternsCache = await dataPluginStart.indexPatterns.getCache();
58+
const indexPatternAtributes = indexPatternsCache.find(pattern => pattern.attributes.title === indexName);
59+
//get all information about this index
60+
const indexPattern = await dataPluginStart.indexPatterns.get(indexPatternAtributes.id);
61+
const field = indexPattern.fields.find(({ name }) => name === fieldName);
62+
const initialSearchSourceState: SearchSourceFields = {
63+
timeout: "1000ms",
64+
terminate_after: 100000,
65+
};
66+
const query = null;
67+
const aggs = termsAgg({
68+
field: indexPattern.fields.getByName("state.keyword"),
69+
size: 100, //null means 10 as default bucket size
70+
direction: 'desc',
71+
query,
72+
});
73+
const filters = [];
74+
const create = dataPluginStart.search.searchSource.create;
75+
const searchSource = create(initialSearchSourceState);
76+
searchSource.setParent(undefined);
77+
const useTimeFilter = false;
78+
const timefilter = null;
79+
searchSource.setField('filter', () => {
80+
const activeFilters = [...filters];
81+
if (useTimeFilter) {
82+
const filter = timefilter.createFilter(indexPattern);
83+
if (filter) {
84+
activeFilters.push(filter);
85+
}
86+
}
87+
return activeFilters;
88+
});
89+
searchSource.setField('size', 0);
90+
searchSource.setField('index', indexPattern);
91+
searchSource.setField('aggs', aggs);
92+
const abortController = new AbortController();
93+
const abortSignal = abortController.signal;
94+
let resp;
95+
try {
96+
resp = await searchSource.fetch(abortSignal);
97+
} catch (error) {
98+
// If the fetch was aborted then no need to surface this error in the UI
99+
if (error.name === 'AbortError')
100+
return;
101+
this.disable('Unable to fetch terms, error: {error.message}'); //TODO
102+
return;
103+
}
104+
const selectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map((bucket: any) => {
105+
return bucket?.key;
106+
});
107+
108+
if (selectOptions.length === 0) {
109+
this.disable('No value to display'); //TODO noValuesDisableMsg(fieldName, indexPattern.title));
110+
return;
111+
}
112+
113+
return selectOptions;
114+
}
115+

vis_type_custom_form_filter_accounts/public/plugin.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface CustomFormFilterAccountsPluginSetupDependencies {
3434
}
3535

3636
export interface CustomFormFilterAccountsVisDependencies extends Partial<CoreStart> {
37+
coreSetup: CoreSetup;
3738
timefilter: TimefilterContract;
3839
filterManager: FilterManager;
3940
}
@@ -46,10 +47,11 @@ export class CustomFormFilterAccountsPlugin implements Plugin<void, CustomFormFi
4647
this.initializerContext = initializerContext;
4748
}
4849

49-
public setup(core: CoreSetup, { expressions, visualizations, data }: CustomFormFilterAccountsPluginSetupDependencies) {
50+
public setup(coreSetup: CoreSetup, { expressions, visualizations, data }: CustomFormFilterAccountsPluginSetupDependencies) {
5051
const dependencies: CustomFormFilterAccountsVisDependencies = {
5152
timefilter: data.query.timefilter.timefilter,
5253
filterManager: data.query.filterManager,
54+
coreSetup: coreSetup,
5355
};
5456
visualizations.createReactVisualization(getCustomFormFilterAccountsVisDefinition(dependencies));
5557
//expressions.registerFunction(createCustomFormFilterAccountsVisFn);

vis_type_custom_form_filter_accounts/public/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
*/
1919

2020
import { TimefilterContract, FilterManager } from '../../../src/plugins/data/public';
21+
import { CoreSetup } from '../../../src/core/public';
2122

2223
export interface CustomFormFilterAccountsVisParams {
2324
filterCounter: number;
@@ -26,4 +27,5 @@ export interface CustomFormFilterAccountsVisParams {
2627
maximumBalance: number;
2728
timefilter: TimefilterContract;
2829
filterManager: FilterManager;
30+
coreSetup: CoreSetup;
2931
}

0 commit comments

Comments
 (0)