Skip to content

Commit

Permalink
[ML] fix types, adjust sorting logic for model plot results
Browse files Browse the repository at this point in the history
  • Loading branch information
darnautov committed Oct 27, 2020
1 parent 4855208 commit d2710c9
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 60 deletions.
20 changes: 12 additions & 8 deletions x-pack/plugins/ml/common/types/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import { EntityFieldType } from './anomalies';

export const ML_PARTITION_FIELDS_CONFIG = 'ml.singleMetricViewer.partitionFields';

export type PartitionFieldConfig = {
anomalousOnly?: boolean;
sort?: {
by: string; // 'anomaly_score' | 'name';
order?: string; // 'asc' | 'desc';
};
} | null;
export type PartitionFieldConfig =
| {
anomalousOnly: boolean;
sort: {
by: string; // 'anomaly_score' | 'name';
order: string; // 'asc' | 'desc';
};
}
| undefined;

export type PartitionFieldsConfig = Partial<Record<EntityFieldType, PartitionFieldConfig>> | null;
export type PartitionFieldsConfig =
| Partial<Record<EntityFieldType, PartitionFieldConfig>>
| undefined;

export type MlStorage = Partial<{
[ML_PARTITION_FIELDS_CONFIG]: PartitionFieldsConfig;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ import {
EuiToolTip,
EuiIcon,
EuiFlexGroup,
EuiRadioGroupOption,
} from '@elastic/eui';
import { EntityFieldType } from '../../../../../common/types/anomalies';
import { PartitionFieldConfig } from '../../../../../common/types/storage';
import { DeepPartial } from '../../../../../common/types/common';
import { UiPartitionFieldConfig } from '../series_controls/series_controls';

export interface Entity {
fieldName: string;
Expand All @@ -47,8 +47,8 @@ export interface EntityControlProps {
entityFieldValueChanged: (entity: Entity, fieldValue: any) => void;
isLoading: boolean;
onSearchChange: (entity: Entity, queryTerm: string) => void;
config: PartitionFieldConfig;
onConfigChange: (fieldType: EntityFieldType, config: DeepPartial<PartitionFieldConfig>) => void;
config: UiPartitionFieldConfig;
onConfigChange: (fieldType: EntityFieldType, config: Partial<UiPartitionFieldConfig>) => void;
forceSelection: boolean;
options: Array<EuiComboBoxOptionOption<string>>;
isModelPlotEnabled: boolean;
Expand Down Expand Up @@ -141,22 +141,25 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt
return label === EMPTY_FIELD_VALUE_LABEL ? <i>{label}</i> : label;
};

private readonly sortOptions = [
{
id: 'anomaly_score',
label: i18n.translate('xpack.ml.timeSeriesExplorer.sortByScoreLabel', {
defaultMessage: 'Anomaly score',
}),
},
{
id: 'name',
label: i18n.translate('xpack.ml.timeSeriesExplorer.sortByNameLabel', {
defaultMessage: 'Name',
}),
},
];
getSortOptions = (): EuiRadioGroupOption[] => {
return [
{
id: 'anomaly_score',
label: i18n.translate('xpack.ml.timeSeriesExplorer.sortByScoreLabel', {
defaultMessage: 'Anomaly score',
}),
disabled: !this.props.config?.anomalousOnly && this.props.isModelPlotEnabled,
},
{
id: 'name',
label: i18n.translate('xpack.ml.timeSeriesExplorer.sortByNameLabel', {
defaultMessage: 'Name',
}),
},
];
};

private readonly orderOptions = [
private readonly orderOptions: EuiRadioGroupOption[] = [
{
id: 'asc',
label: i18n.translate('xpack.ml.timeSeriesExplorer.ascOptionsOrderLabel', {
Expand Down Expand Up @@ -234,8 +237,13 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt
}
checked={!!this.props.config?.anomalousOnly}
onChange={(e) => {
const isAnomalousOnly = e.target.checked;
this.props.onConfigChange(this.props.entity.fieldType, {
anomalousOnly: e.target.checked,
anomalousOnly: isAnomalousOnly,
sort: {
order: this.props.config.sort.order,
by: this.props.config.sort.by,
},
});
}}
compressed
Expand Down Expand Up @@ -276,12 +284,12 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt
}
>
<EuiRadioGroup
options={this.sortOptions}
options={this.getSortOptions()}
idSelected={this.props.config?.sort?.by}
onChange={(id) => {
this.props.onConfigChange(this.props.entity.fieldType, {
sort: {
...(this.props.config?.sort ? this.props.config.sort : {}),
order: this.props.config.sort.order,
by: id,
},
});
Expand All @@ -304,7 +312,7 @@ export class EntityControl extends Component<EntityControlProps, EntityControlSt
onChange={(id) => {
this.props.onConfigChange(this.props.entity.fieldType, {
sort: {
...(this.props.config?.sort ? this.props.config.sort : {}),
by: this.props.config.sort.by,
order: id,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getControlsForDetector } from '../../get_controls_for_detector';
import { getViewableDetectors } from '../../timeseriesexplorer';
import {
ML_PARTITION_FIELDS_CONFIG,
PartitionFieldConfig,
PartitionFieldsConfig,
} from '../../../../../common/types/storage';
import { useStorage } from '../../../contexts/ml/use_storage';
Expand All @@ -34,14 +35,19 @@ function getEntityControlOptions(fieldValues: any[]) {
});
}

type UiPartitionFieldsConfig = Exclude<PartitionFieldsConfig, null>;
export type UiPartitionFieldsConfig = Exclude<PartitionFieldsConfig, undefined>;

export type UiPartitionFieldConfig = Exclude<PartitionFieldConfig, undefined>;

/**
* Provides default fields configuration.
*/
const getDefaultFieldConfig = (fieldTypes: EntityFieldType[]): UiPartitionFieldsConfig => {
const getDefaultFieldConfig = (
fieldTypes: EntityFieldType[],
isAnomalousOnly: boolean
): UiPartitionFieldsConfig => {
return fieldTypes.reduce((acc, f) => {
acc[f] = { anomalousOnly: true, sort: { by: 'anomaly_score', order: 'desc' } };
acc[f] = { anomalousOnly: isAnomalousOnly, sort: { by: 'anomaly_score', order: 'desc' } };
return acc;
}, {} as UiPartitionFieldsConfig);
};
Expand Down Expand Up @@ -91,18 +97,22 @@ export const SeriesControls: FC<SeriesControlsProps> = ({
return getControlsForDetector(selectedDetectorIndex, selectedEntities, selectedJobId);
}, [selectedDetectorIndex, selectedEntities, selectedJobId]);

const [partitionFieldsConfig, setPartitionFieldsConfig] = useStorage<UiPartitionFieldsConfig>(
ML_PARTITION_FIELDS_CONFIG,
getDefaultFieldConfig(entityControls.map((v) => v.fieldType))
const [storageFieldsConfig, setStorageFieldsConfig] = useStorage<PartitionFieldsConfig>(
ML_PARTITION_FIELDS_CONFIG
);

// Merge the default config with the one from the local storage
const resultFieldsConfig = useMemo(() => {
return {
...getDefaultFieldConfig(entityControls.map((v) => v.fieldType)),
...partitionFieldsConfig,
...getDefaultFieldConfig(
entityControls.map((v) => v.fieldType),
!storageFieldsConfig
? true
: Object.values(storageFieldsConfig).some((v) => !!v?.anomalousOnly)
),
...(!storageFieldsConfig ? {} : storageFieldsConfig),
};
}, [entityControls, partitionFieldsConfig]);
}, [entityControls, storageFieldsConfig]);

/**
* Loads available entity values.
Expand Down Expand Up @@ -203,17 +213,32 @@ export const SeriesControls: FC<SeriesControlsProps> = ({
text: d.detector_description,
}));

const onConfigChange: EntityControlProps['onConfigChange'] = useCallback(
const onFieldConfigChange: EntityControlProps['onConfigChange'] = useCallback(
(fieldType, config) => {
setPartitionFieldsConfig({
...resultFieldsConfig,
[fieldType]: {
...(resultFieldsConfig[fieldType] ? resultFieldsConfig[fieldType] : {}),
...config,
},
const updatedFieldConfig = {
...(resultFieldsConfig[fieldType] ? resultFieldsConfig[fieldType] : {}),
...config,
} as UiPartitionFieldConfig;

const updatedResultConfig = { ...resultFieldsConfig };

if (resultFieldsConfig[fieldType]?.anomalousOnly !== updatedFieldConfig.anomalousOnly) {
// In case anomalous selector has been changed
// we need to change it for all the other fields
for (const c in updatedResultConfig) {
if (updatedResultConfig.hasOwnProperty(c)) {
updatedResultConfig[c as EntityFieldType]!.anomalousOnly =
updatedFieldConfig.anomalousOnly;
}
}
}

setStorageFieldsConfig({
...updatedResultConfig,
[fieldType]: updatedFieldConfig,
});
},
[resultFieldsConfig, setPartitionFieldsConfig]
[resultFieldsConfig, setStorageFieldsConfig]
);

/** Indicates if any of the previous controls is empty */
Expand Down Expand Up @@ -250,7 +275,7 @@ export const SeriesControls: FC<SeriesControlsProps> = ({
isLoading={entitiesLoading}
onSearchChange={entityFieldSearchChanged}
config={resultFieldsConfig[entity.fieldType]!}
onConfigChange={onConfigChange}
onConfigChange={onFieldConfigChange}
forceSelection={forceSelection}
key={entityKey}
options={getEntityControlOptions(entityValues[entity.fieldName])}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,24 @@ type SearchTerm =
/**
* Gets an object for aggregation query to retrieve field name and values.
* @param fieldType - Field type
* @param isModelPlotSearch
* @param query - Optional query string for partition value
* @param fieldConfig - Optional config for filtering and sorting
* @returns {Object}
*/
function getFieldAgg(fieldType: PartitionFieldsType, query?: string, fieldConfig?: FieldConfig) {
function getFieldAgg(
fieldType: PartitionFieldsType,
isModelPlotSearch: boolean,
query?: string,
fieldConfig?: FieldConfig
) {
const AGG_SIZE = 100;

const fieldNameKey = `${fieldType}_name`;
const fieldValueKey = `${fieldType}_value`;

const sortByField = fieldConfig?.sort?.by === 'name' ? '_key' : 'anomaly_score';
const sortByField =
fieldConfig?.sort?.by === 'name' || isModelPlotSearch ? '_key' : 'anomaly_score';

return {
[fieldNameKey]: {
Expand Down Expand Up @@ -149,6 +156,7 @@ export const getPartitionFieldsValuesFactory = ({ asInternalUser }: IScopedClust
return !!v?.anomalousOnly;
}
);
const isModelPlotSearch = !!(isModelPlotEnabled && !isAnomalousOnly);

const requestBody = {
query: {
Expand Down Expand Up @@ -184,7 +192,7 @@ export const getPartitionFieldsValuesFactory = ({ asInternalUser }: IScopedClust
: []),
{
term: {
result_type: isModelPlotEnabled && !isAnomalousOnly ? 'model_plot' : 'record',
result_type: isModelPlotSearch ? 'model_plot' : 'record',
},
},
],
Expand All @@ -194,7 +202,7 @@ export const getPartitionFieldsValuesFactory = ({ asInternalUser }: IScopedClust
...PARTITION_FIELDS.reduce((acc, key) => {
return {
...acc,
...getFieldAgg(key, searchTerm[key], fieldsConfig[key]),
...getFieldAgg(key, isModelPlotSearch, searchTerm[key], fieldsConfig[key]),
};
}, {}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,11 @@ export const categoryExamplesSchema = schema.object({

const fieldConfig = schema.maybe(
schema.object({
anomalousOnly: schema.maybe(schema.boolean()),
sort: schema.maybe(
schema.object({
by: schema.string(),
order: schema.maybe(schema.string()),
})
),
anomalousOnly: schema.boolean(),
sort: schema.object({
by: schema.string(),
order: schema.maybe(schema.string()),
}),
})
);

Expand Down

0 comments on commit d2710c9

Please sign in to comment.