Skip to content

Commit

Permalink
[indexPatterns/create] refactor time field options (elastic#11996)
Browse files Browse the repository at this point in the history
* [indexPatterns/create] timeFields -> timeFieldOptions

In elastic#11409 we added two new possible options to the "Time Filter field name" drop down. These options display a message that "there are no time fields" or "I don't want to use the time filter". These new options were added to the `controller.timeFields` array, which is where the other options were, but they don't actually represent time fields.

This change updates `controller.timeFields` to instead be a list of options, specifically created to populate the select box in the view. They are now stored at `controller.timeFieldOptions` and each option has a `display` property (which will be rendered in the view) and optionally a `fieldName` property. The selected option, `controller.formValues.timeFieldOption` is checked for a `fieldName` property when the Index Pattern is created.

* [indexPatterns/create] populate `this.timeFieldOptions`

* [indexPatterns/create] unify fetching and errors for timeFieldOptions

* [indexPatterns/create] refreshFieldList() -> refreshTimeFieldOptions()

* [indexPatterns/create] unify "No time fields available" messages

* [indexPatterns/create] disable the timeField select when loading

* [indexPatterns/create] only require a time field is there are options

Doing this prevents the time field from rendering as invalid when there is no matching index pattern (which has it's own error display) and when the fields are loading

* [indexPatterns/create] isolate side-effects

* [indexPatterns/create] let refreshTimeFieldOptions() control model

* [indexPatterns/create] attempt to make default field selection clearer

* [indexPatterns/create] explain why we set timeFieldOption

* [indexPatterns/create] make timeFieldName `undefined` when not set

* [indexPatterns/create] Don't pass two args to .then() unless needed

* 💅
  • Loading branch information
spalger authored and snide committed May 30, 2017
1 parent 6896040 commit 76f833f
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@
<!-- Input error text -->
<div
class="kuiVerticalRhythm"
ng-if="controller.fetchFieldsError"
ng-if="controller.timeFieldOptionsError"
>
<p class="kuiText">
<span class="kuiStatusText kuiStatusText--error">
<span class="kuiStatusText__icon kuiIcon fa-warning"></span>
{{controller.fetchFieldsError}}
{{controller.timeFieldOptionsError}}
</span>
</p>
</div>
Expand Down Expand Up @@ -105,7 +105,7 @@
<small>
<a
class="kuiLink"
ng-click="controller.refreshFieldList();"
ng-click="controller.refreshTimeFieldOptions();"
translate="KIBANA-REFRESH_FIELDS"
></a>
</small>
Expand All @@ -115,17 +115,11 @@
<select
class="kuiSelect kuiSelect--large kuiVerticalRhythmSmall"
data-test-subj="createIndexPatternTimeFieldSelect"
ng-disabled="controller.fetchFieldsError || controller.dateFields.length === 1"
ng-required="!controller.fetchFieldsError"
ng-options="field.name for field in controller.dateFields"
ng-model="controller.formValues.timeField"
ng-disabled="controller.isLoading() || controller.timeFieldOptionsError || controller.timeFieldOptions.length === 1"
ng-required="controller.timeFieldOptions.length"
ng-options="option.display for option in controller.timeFieldOptions"
ng-model="controller.formValues.timeFieldOption"
></select>

<p
class="kuiSubText kuiVerticalRhythmSmall"
ng-if="!controller.fetchFieldsError && !controller.indexHasDateFields"
translate="KIBANA-INDICES_DONT_CONTAIN_TIME_FIELDS"
></p>
</div>
</div>

Expand Down Expand Up @@ -347,7 +341,7 @@
<!-- Form actions -->
<button
data-test-subj="createIndexPatternCreateButton"
ng-disabled="form.$invalid || controller.fetchFieldsError || controller.isLoading()"
ng-disabled="form.$invalid || controller.timeFieldOptionsError || controller.isLoading()"
class="kuiButton kuiButton--primary kuiVerticalRhythm"
type="submit"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,23 @@ uiModules.get('apps/management')
nameIsPattern: false,
expandable: false,
nameInterval: _.find(intervals, { name: 'daily' }),
timeField: null,
timeFieldOption: null,
};

// UI state.
this.dateFields = null;
this.timeFieldOptions = [];
this.timeFieldOptionsError = null;
this.sampleCount = 5;
this.samples = null;
this.existing = null;
this.nameIntervalOptions = intervals;
this.patternErrors = [];
this.fetchFieldsError = null;

const TIME_FILTER_FIELD_OPTIONS = {
NO_DATE_FIELD_DESIRED: {
name: $translate.instant('KIBANA-NO_DATE_FIELD_DESIRED')
},
NO_DATE_FIELDS_IN_INDICES: {
name: $translate.instant('KIBANA-NO_DATE_FIELDS_IN_INDICES')
}
};

const fetchFieldList = () => {
this.dateFields = null;
this.formValues.timeField = null;
let fetchFieldsError;
let dateFields;

const getTimeFieldOptions = () => {
const missingPattern = !this.formValues.name;
const missingInterval = this.formValues.nameIsPattern && !this.formValues.nameInterval;
if (missingPattern || missingInterval) {
return;
return Promise.resolve({ options: [] });
}

loadingCount += 1;
Expand All @@ -69,79 +55,82 @@ uiModules.get('apps/management')

return indexPatterns.mapper.getFieldsForIndexPattern(pattern, {
skipIndexPatternCache: true,
})
.catch((err) => {
// TODO: we should probably display a message of some kind
if (err instanceof IndexPatternMissingIndices) {
fetchFieldsError = $translate.instant('KIBANA-INDICES_MATCH_PATTERN');
return [];
}

throw err;
});
})
.then(fields => {
if (fields.length > 0) {
fetchFieldsError = null;
dateFields = fields.filter(field => field.type === 'date');
const dateFields = fields.filter(field => field.type === 'date');

if (dateFields.length === 0) {
return {
options: [
{
display: $translate.instant('KIBANA-INDICES_DONT_CONTAIN_TIME_FIELDS')
}
]
};
}

return {
fetchFieldsError,
dateFields,
options: [
{
display: $translate.instant('KIBANA-NO_DATE_FIELD_DESIRED')
},
...dateFields.map(field => ({
display: field.name,
fieldName: field.name
})),
]
};
}, notify.fatal)
})
.catch(err => {
if (err instanceof IndexPatternMissingIndices) {
return {
error: $translate.instant('KIBANA-INDICES_MATCH_PATTERN')
};
}

throw err;
})
.finally(() => {
loadingCount -= 1;
});
};

const updateFieldList = results => {
this.fetchFieldsError = results.fetchFieldsError;
if (this.fetchFieldsError) {
return;
}
const findTimeFieldOption = match => {
if (!match) return;

this.dateFields = results.dateFields || [];
this.indexHasDateFields = this.dateFields.length > 0;
const moreThanOneDateField = this.dateFields.length > 1;
if (this.indexHasDateFields) {
this.dateFields.unshift(TIME_FILTER_FIELD_OPTIONS.NO_DATE_FIELD_DESIRED);
} else {
this.dateFields.unshift(TIME_FILTER_FIELD_OPTIONS.NO_DATE_FIELDS_IN_INDICES);
}

if (!moreThanOneDateField) {
// At this point the `dateFields` array contains the date fields and the "no selection"
// option. When we have less than two date fields we choose the last option, which will
// be the "no date fields available" option if there are zero date fields, or the only
// date field if there is one.
this.formValues.timeField = this.dateFields[this.dateFields.length - 1];
}
return this.timeFieldOptions.find(option => (
// comparison is not done with _.isEqual() because options get a unique
// `$$hashKey` tag attached to them by ng-repeat
option.fieldName === match.fieldName &&
option.display === match.display
));
};

const updateFieldListAndSetTimeField = (results, timeFieldName) => {
updateFieldList(results);

if (!results.dateFields.length) {
return;
const pickDefaultTimeFieldOption = () => {
const noOptions = this.timeFieldOptions.length === 0;
// options that represent a time field
const fieldOptions = this.timeFieldOptions.filter(option => !!option.fieldName);
// options like "I don't want the time filter" or "There are no date fields"
const nonFieldOptions = this.timeFieldOptions.filter(option => !option.fieldName);
// if there are multiple field or non-field options then we can't select a default, the user must choose
const tooManyOptions = fieldOptions.length > 1 || nonFieldOptions.length > 1;

if (noOptions || tooManyOptions) {
return null;
}

const matchingTimeField = results.dateFields.find(field => field.name === timeFieldName);

//assign the field from the results-list
//angular recreates a new timefield instance, each time the list is refreshed.
//This ensures the selected field matches one of the instances in the list.
if (matchingTimeField) {
this.formValues.timeField = matchingTimeField;
if (fieldOptions.length === 1) {
return fieldOptions[0];
}

return nonFieldOptions[0];
};

const resetIndex = () => {
this.patternErrors = [];
this.samples = null;
this.existing = null;
this.fetchFieldsError = null;
};

function mockIndexPattern(index) {
Expand Down Expand Up @@ -207,39 +196,57 @@ uiModules.get('apps/management')
return loadingCount > 0;
};

this.refreshFieldList = () => {
const timeField = this.formValues.timeField;
this.refreshTimeFieldOptions = () => {
const prevOption = this.formValues.timeFieldOption;

loadingCount += 1;
fetchFieldList().then(results => {
if (timeField) {
updateFieldListAndSetTimeField(results, timeField.name);
} else {
updateFieldList(results);
}
}).finally(() => {
loadingCount -= 1;
});
this.timeFieldOptions = [];
this.timeFieldOptionsError = null;
this.formValues.timeFieldOption = null;
getTimeFieldOptions()
.then(({ options, error }) => {
this.timeFieldOptions = options;
this.timeFieldOptionsError = error;
if (!this.timeFieldOptions) {
return;
}

// Restore the preivously selected state, or select the default option in the UI
const restoredOption = findTimeFieldOption(prevOption);
const defaultOption = pickDefaultTimeFieldOption();
this.formValues.timeFieldOption = restoredOption || defaultOption;
})
.catch(notify.error)
.finally(() => {
loadingCount -= 1;
});
};

this.createIndexPattern = () => {
const id = this.formValues.name;
let timeFieldName;
if ((this.formValues.timeField !== TIME_FILTER_FIELD_OPTIONS.NO_DATE_FIELD_DESIRED)
&& (this.formValues.timeField !== TIME_FILTER_FIELD_OPTIONS.NO_DATE_FIELDS_IN_INDICES)) {
timeFieldName = this.formValues.timeField.name;
}

// Only event-time-based index patterns set an intervalName.
const intervalName =
this.formValues.nameIsPattern
? this.formValues.nameInterval.name
const {
name,
timeFieldOption,
nameIsPattern,
nameInterval,
expandable
} = this.formValues;

const id = name;

const timeFieldName = timeFieldOption
? timeFieldOption.fieldName
: undefined;

const notExpandable =
!this.formValues.expandable && this.canExpandIndices()
// this seems wrong, but it's the original logic... https://git.io/vHYFo
const notExpandable = (!expandable && this.canExpandIndices())
? true
: undefined;

// Only event-time-based index patterns set an intervalName.
const intervalName = (nameIsPattern && nameInterval)
? nameInterval.name
: undefined;

loadingCount += 1;
sendCreateIndexPatternRequest(indexPatterns, {
id,
Expand Down Expand Up @@ -287,7 +294,6 @@ uiModules.get('apps/management')

if (!nameIsPattern) {
delete this.formValues.nameInterval;
delete this.formValues.timeField;
} else {
this.formValues.nameInterval = this.formValues.nameInterval || intervals.byName.days;
this.formValues.name = this.formValues.name || getDefaultPatternForInterval(this.formValues.nameInterval);
Expand Down Expand Up @@ -330,28 +336,23 @@ uiModules.get('apps/management')
.finally(() => {
// prevent running when no change happened (ie, first watcher call)
if (!_.isEqual(newVal, oldVal)) {
fetchFieldList().then(results => {
if (lastPromise === samplePromise) {
updateFieldList(results);
samplePromise = null;
}
});
this.refreshTimeFieldOptions();
}
});
});

$scope.$watchMulti([
'controller.sampleCount'
], () => {
this.refreshFieldList();
this.refreshTimeFieldOptions();
});

$scope.$watchMulti([
'controller.isLoading()',
'form.name.$error.indexNameInput',
'controller.formValues.timeField'
], ([loading, invalidIndexName, timeField]) => {
const state = { loading, invalidIndexName, timeField };
'controller.formValues.timeFieldOption'
], ([loading, invalidIndexName, timeFieldOption]) => {
const state = { loading, invalidIndexName, timeFieldOption };
this.createButtonText = pickCreateButtonText($translate, state);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export function pickCreateButtonText($translate, state) {
const {
loading,
invalidIndexName,
timeField
timeFieldOption
} = state;

if (loading) {
Expand All @@ -13,7 +13,7 @@ export function pickCreateButtonText($translate, state) {
return $translate.instant('KIBANA-INVALID_INDEX_PATTERN');
}

if (!timeField) {
if (!timeFieldOption) {
return $translate.instant('KIBANA-FIELD_IS_REQUIRED', {
fieldName: $translate.instant('KIBANA-TIME_FILTER_FIELD_NAME')
});
Expand Down
1 change: 0 additions & 1 deletion src/core_plugins/kibana/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"KIBANA-MORE": "more",
"KIBANA-TIME_FILTER_FIELD_NAME": "Time Filter field name",
"KIBANA-NO_DATE_FIELD_DESIRED": "I don't want to use the Time Filter",
"KIBANA-NO_DATE_FIELDS_IN_INDICES": "None available",
"KIBANA-REFRESH_FIELDS": "refresh fields",
"KIBANA-INDICES_DONT_CONTAIN_TIME_FIELDS": "The indices which match this index pattern don't contain any time fields.",
"KIBANA-INVALID_INDEX_PATTERN": "Invalid index name pattern.",
Expand Down

0 comments on commit 76f833f

Please sign in to comment.