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

[ML] Transforms: Support for terms agg in pivot configurations. #123634

Merged
merged 7 commits into from
Jan 27, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
1 change: 1 addition & 0 deletions x-pack/plugins/transform/common/types/pivot_aggs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const PIVOT_SUPPORTED_AGGS = {
VALUE_COUNT: 'value_count',
FILTER: 'filter',
TOP_METRICS: 'top_metrics',
TERMS: 'terms',
} as const;

export type PivotSupportedAggs = typeof PIVOT_SUPPORTED_AGGS[keyof typeof PIVOT_SUPPORTED_AGGS];
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/transform/public/app/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export {
getEsAggFromAggConfig,
isPivotAggsConfigWithUiSupport,
isPivotAggsConfigPercentiles,
isPivotAggsConfigTerms,
PERCENTILES_AGG_DEFAULT_PERCENTS,
TERMS_AGG_DEFAULT_SIZE,
pivotAggsFieldSupport,
} from './pivot_aggs';
export type {
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/transform/public/app/common/pivot_aggs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export function isPivotSupportedAggs(arg: unknown): arg is PivotSupportedAggs {
}

export const PERCENTILES_AGG_DEFAULT_PERCENTS = [1, 5, 25, 50, 75, 95, 99];
export const TERMS_AGG_DEFAULT_SIZE = 10;

export const pivotAggsFieldSupport = {
[KBN_FIELD_TYPES.ATTACHMENT]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
Expand All @@ -45,6 +46,7 @@ export const pivotAggsFieldSupport = {
PIVOT_SUPPORTED_AGGS.VALUE_COUNT,
PIVOT_SUPPORTED_AGGS.FILTER,
PIVOT_SUPPORTED_AGGS.TOP_METRICS,
PIVOT_SUPPORTED_AGGS.TERMS,
],
[KBN_FIELD_TYPES.MURMUR3]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
[KBN_FIELD_TYPES.NUMBER]: [
Expand All @@ -63,6 +65,7 @@ export const pivotAggsFieldSupport = {
PIVOT_SUPPORTED_AGGS.VALUE_COUNT,
PIVOT_SUPPORTED_AGGS.FILTER,
PIVOT_SUPPORTED_AGGS.TOP_METRICS,
PIVOT_SUPPORTED_AGGS.TERMS,
],
[KBN_FIELD_TYPES._SOURCE]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
[KBN_FIELD_TYPES.UNKNOWN]: [PIVOT_SUPPORTED_AGGS.VALUE_COUNT, PIVOT_SUPPORTED_AGGS.FILTER],
Expand Down Expand Up @@ -226,9 +229,15 @@ interface PivotAggsConfigPercentiles extends PivotAggsConfigWithUiBase {
percents: number[];
}

interface PivotAggsConfigTerms extends PivotAggsConfigWithUiBase {
agg: typeof PIVOT_SUPPORTED_AGGS.TERMS;
size: number;
}

export type PivotAggsConfigWithUiSupport =
| PivotAggsConfigWithUiBase
| PivotAggsConfigPercentiles
| PivotAggsConfigTerms
| PivotAggsConfigWithExtendedForm;

export function isPivotAggsConfigWithUiSupport(arg: unknown): arg is PivotAggsConfigWithUiSupport {
Expand Down Expand Up @@ -258,6 +267,10 @@ export function isPivotAggsConfigPercentiles(arg: unknown): arg is PivotAggsConf
);
}

export function isPivotAggsConfigTerms(arg: unknown): arg is PivotAggsConfigTerms {
return isPopulatedObject(arg, ['agg', 'field', 'size']) && arg.agg === PIVOT_SUPPORTED_AGGS.TERMS;
}

export type PivotAggsConfig = PivotAggsConfigBase | PivotAggsConfigWithUiSupport;

export type PivotAggsConfigWithUiSupportDict = Dictionary<PivotAggsConfigWithUiSupport>;
Expand Down
12 changes: 8 additions & 4 deletions x-pack/plugins/transform/public/app/hooks/use_pivot_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { EuiDataGridColumn } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { getFlattenedObject } from '@kbn/std';

import { sample, difference } from 'lodash';
import { difference } from 'lodash';
import { ES_FIELD_TYPES } from '../../../../../../src/plugins/data/common';

import type { PreviewMappingsProperties } from '../../../common/api_schemas/transforms';
Expand Down Expand Up @@ -79,12 +79,16 @@ export function getCombinedProperties(
populatedProperties: PreviewMappingsProperties,
docs: Array<Record<string, unknown>>
): PreviewMappingsProperties {
// Take a sample from docs and resolve missing mappings
const sampleDoc = sample(docs) ?? {};
const missingMappings = difference(Object.keys(sampleDoc), Object.keys(populatedProperties));
// Identify missing mappings
const missingMappings = difference(
Copy link
Contributor

Choose a reason for hiding this comment

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

The grid isn't inferring the type of the value correctly - it thinks it's a string, so left aligns the cell contents, and gives the alphabetical sort options (note sort isn't working on nested fields but I will raise a separate issue for that).

image

Copy link
Contributor

Choose a reason for hiding this comment

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

It makes sense to address this as part of #123796 as even if the type for the terms agg columns is correctly set to numeric it still won't be sorted correctly as the column name will contain a dot.

// Create an array of unique flattened field names across all docs
[...new Set(docs.flatMap(Object.keys))],
Object.keys(populatedProperties)
);
return {
...populatedProperties,
...missingMappings.reduce((acc, curr) => {
const sampleDoc = docs.find((d) => typeof d[curr] !== 'undefined') ?? {};
acc[curr] = { type: typeof sampleDoc[curr] as ES_FIELD_TYPES };
return acc;
}, {} as PreviewMappingsProperties),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EuiButton,
EuiCodeBlock,
EuiComboBox,
EuiFieldNumber,
EuiFieldText,
EuiForm,
EuiFormRow,
Expand All @@ -32,9 +33,11 @@ import {
import {
isAggName,
isPivotAggsConfigPercentiles,
isPivotAggsConfigTerms,
isPivotAggsConfigWithUiSupport,
getEsAggFromAggConfig,
PERCENTILES_AGG_DEFAULT_PERCENTS,
TERMS_AGG_DEFAULT_SIZE,
PivotAggsConfig,
PivotAggsConfigWithUiSupportDict,
} from '../../../../common';
Expand Down Expand Up @@ -75,6 +78,30 @@ function parsePercentsInput(inputValue: string | undefined) {
return [];
}

// Input string should only include comma separated numbers
function isValidPercentsInput(inputValue: string) {
return /^[0-9]+(,[0-9]+)*$/.test(inputValue);
}

function getDefaultSize(defaultData: PivotAggsConfig): number | undefined {
if (isPivotAggsConfigTerms(defaultData)) {
return defaultData.size;
}
}

function parseSizeInput(inputValue: string | undefined) {
if (inputValue !== undefined && isValidSizeInput(inputValue)) {
return parseInt(inputValue, 10);
}

return TERMS_AGG_DEFAULT_SIZE;
}

// Input string should only include numbers
function isValidSizeInput(inputValue: string) {
return /^\d+$/.test(inputValue);
}

export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onChange, options }) => {
const [aggConfigDef, setAggConfigDef] = useState(cloneDeep(defaultData));

Expand All @@ -85,6 +112,9 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
);

const [percents, setPercents] = useState(getDefaultPercents(defaultData));
const [validPercents, setValidPercents] = useState(agg === PIVOT_SUPPORTED_AGGS.PERCENTILES);
const [size, setSize] = useState(getDefaultSize(defaultData));
const [validSize, setValidSize] = useState(agg === PIVOT_SUPPORTED_AGGS.TERMS);

const isUnsupportedAgg = !isPivotAggsConfigWithUiSupport(defaultData);

Expand Down Expand Up @@ -118,10 +148,19 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
if (aggVal === PIVOT_SUPPORTED_AGGS.PERCENTILES && percents === undefined) {
setPercents(PERCENTILES_AGG_DEFAULT_PERCENTS);
}
if (aggVal === PIVOT_SUPPORTED_AGGS.TERMS && size === undefined) {
setSize(TERMS_AGG_DEFAULT_SIZE);
}
}

function updatePercents(inputValue: string) {
setPercents(parsePercentsInput(inputValue));
setValidPercents(isValidPercentsInput(inputValue));
}

function updateSize(inputValue: string) {
setSize(parseSizeInput(inputValue));
setValidSize(isValidSizeInput(inputValue));
}

function getUpdatedItem(): PivotAggsConfig {
Expand All @@ -137,21 +176,29 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
resultField = field[0];
}

if (agg !== PIVOT_SUPPORTED_AGGS.PERCENTILES) {
if (agg === PIVOT_SUPPORTED_AGGS.PERCENTILES) {
updatedItem = {
...aggConfigDef,
agg,
aggName,
field: resultField,
dropDownName: defaultData.dropDownName,
percents,
};
} else if (agg === PIVOT_SUPPORTED_AGGS.TERMS) {
updatedItem = {
agg,
aggName,
field: resultField,
dropDownName: defaultData.dropDownName,
size,
};
} else {
updatedItem = {
...aggConfigDef,
agg,
aggName,
field: resultField,
dropDownName: defaultData.dropDownName,
percents,
};
}

Expand Down Expand Up @@ -202,13 +249,18 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
percentsText = percents.toString();
}

const validPercents =
agg === PIVOT_SUPPORTED_AGGS.PERCENTILES && parsePercentsInput(percentsText).length > 0;
let sizeText;
if (size !== undefined) {
sizeText = size.toString();
}

let formValid = validAggName;
if (formValid && agg === PIVOT_SUPPORTED_AGGS.PERCENTILES) {
formValid = validPercents;
}
if (formValid && agg === PIVOT_SUPPORTED_AGGS.TERMS) {
formValid = validSize;
}
if (isPivotAggsWithExtendedForm(aggConfigDef)) {
formValid = validAggName && aggConfigDef.isValid();
}
Expand Down Expand Up @@ -325,6 +377,23 @@ export const PopoverForm: React.FC<Props> = ({ defaultData, otherAggNames, onCha
/>
</EuiFormRow>
)}
{agg === PIVOT_SUPPORTED_AGGS.TERMS && (
<EuiFormRow
label={i18n.translate('xpack.transform.agg.popoverForm.sizeLabel', {
defaultMessage: 'Size',
})}
error={
!validSize && [
i18n.translate('xpack.transform.groupBy.popoverForm.invalidSizeErrorMessage', {
defaultMessage: 'Enter a valid positive number',
}),
]
}
isInvalid={!validSize}
>
<EuiFieldNumber defaultValue={sizeText} onChange={(e) => updateSize(e.target.value)} />
</EuiFormRow>
)}
{isUnsupportedAgg && (
<EuiCodeBlock
aria-label={i18n.translate('xpack.transform.agg.popoverForm.codeBlock', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,64 @@ export default function ({ getService }: FtrProviderContext) {
discoverQueryHits: '10',
},
} as PivotTransformTestData,
{
type: 'pivot',
suiteTitle: 'batch transform with terms group and terms agg',
source: 'ft_ecommerce',
groupByEntries: [
{
identifier: 'terms(customer_gender)',
label: 'customer_gender',
} as GroupByEntry,
],
aggregationEntries: [
{
identifier: 'terms(geoip.city_name)',
label: 'geoip.city_name.terms',
},
],
transformId: `ec_3_${Date.now()}`,
transformDescription:
'ecommerce batch transform with group by terms(customer_gender) and aggregation terms(geoip.city_name)',
get destinationIndex(): string {
return `user-${this.transformId}`;
},
discoverAdjustSuperDatePicker: false,
expected: {
pivotAdvancedEditorValueArr: ['{', ' "group_by": {', ' "customer_gender": {'],
pivotAdvancedEditorValue: {
group_by: {
customer_gender: {
terms: {
field: 'customer_gender',
},
},
},
aggregations: {
'geoip.city_name': {
terms: {
field: 'geoip.city_name',
size: 3,
},
},
},
},
transformPreview: {
column: 0,
values: ['FEMALE', 'MALE'],
},
row: {
status: TRANSFORM_STATE.STOPPED,
mode: 'batch',
progress: '100',
},
indexPreview: {
columns: 10,
rows: 5,
},
discoverQueryHits: '2',
},
} as PivotTransformTestData,
{
type: 'latest',
suiteTitle: 'batch transform with the latest function',
Expand All @@ -351,7 +409,7 @@ export default function ({ getService }: FtrProviderContext) {
identifier: 'order_date',
label: 'order_date',
},
transformId: `ec_3_${Date.now()}`,
transformId: `ec_4_${Date.now()}`,

transformDescription:
'ecommerce batch transform with the latest function config, sort by order_data, country code as unique key',
Expand Down