{"version":3,"file":"groupBy.cjs","sources":["../../../../src/transformations/transformers/groupBy.ts"],"sourcesContent":["import { map } from 'rxjs/operators';\n\nimport { getFieldDisplayName } from '../../field/fieldState';\nimport { DataFrame, Field } from '../../types/dataFrame';\nimport { DataTransformerInfo, TransformationApplicabilityLevels } from '../../types/transformations';\nimport { getFieldTypeForReducer, reduceField, ReducerID } from '../fieldReducer';\nimport { getFieldMatcher } from '../matchers';\nimport { FieldMatcherID } from '../matchers/ids';\n\nimport { DataTransformerID } from './ids';\nimport { findMaxFields } from './utils';\n\nconst MINIMUM_FIELDS_REQUIRED = 1;\n\nexport enum GroupByOperationID {\n  aggregate = 'aggregate',\n  groupBy = 'groupby',\n}\n\nexport interface GroupByFieldOptions {\n  aggregations: ReducerID[];\n  operation: GroupByOperationID | null;\n}\n\nexport interface GroupByTransformerOptions {\n  fields: Record<string, GroupByFieldOptions>;\n}\n\ninterface FieldMap {\n  [key: string]: Field;\n}\n\nexport const groupByTransformer: DataTransformerInfo<GroupByTransformerOptions> = {\n  id: DataTransformerID.groupBy,\n  name: 'Group by',\n  description: 'Group the data by a field values then process calculations for each group.',\n  defaultOptions: {\n    fields: {},\n  },\n  isApplicable: (data: DataFrame[]) => {\n    // Group by needs at least two fields\n    // a field to group on and a field to aggregate\n    // We make sure that at least one frame has at\n    // least two fields\n    const maxFields = findMaxFields(data);\n\n    return maxFields >= MINIMUM_FIELDS_REQUIRED\n      ? TransformationApplicabilityLevels.Applicable\n      : TransformationApplicabilityLevels.NotApplicable;\n  },\n  isApplicableDescription: (data: DataFrame[]) => {\n    const maxFields = findMaxFields(data);\n    return `The Group by transformation requires a series with at least ${MINIMUM_FIELDS_REQUIRED} fields to work. The maximum number of fields found on a series is ${maxFields}`;\n  },\n  /**\n   * Return a modified copy of the series. If the transform is not or should not\n   * be applied, just return the input series\n   */\n  operator: (options) => (source) =>\n    source.pipe(\n      map((data) => {\n        const groupByFieldNames: string[] = [];\n\n        for (const [k, v] of Object.entries(options.fields)) {\n          if (v.operation === GroupByOperationID.groupBy) {\n            groupByFieldNames.push(k);\n          }\n        }\n\n        if (groupByFieldNames.length === 0) {\n          return data;\n        }\n\n        const matcher = getFieldMatcher({\n          id: FieldMatcherID.byNames,\n          options: { names: groupByFieldNames },\n        });\n\n        const processed: DataFrame[] = [];\n\n        for (const frame of data) {\n          // Create a list of fields to group on\n          // If there are none we skip the rest\n\n          const groupByFields: Field[] = frame.fields.filter((field) => matcher(field, frame, data));\n\n          if (groupByFields.length === 0) {\n            continue;\n          }\n\n          // Group the values by fields and groups so we can get all values for a\n          // group for a given field.\n          const valuesByGroupKey = groupValuesByKey(frame, groupByFields);\n\n          // Add the grouped fields to the resulting fields of the transformation\n          const fields: Field[] = createGroupedFields(groupByFields, valuesByGroupKey);\n\n          // Then for each calculations configured, compute and add a new field (column)\n          for (const field of frame.fields) {\n            if (!shouldCalculateField(field, options)) {\n              continue;\n            }\n\n            const fieldName = getFieldDisplayName(field);\n            const aggregations = options.fields[fieldName].aggregations;\n            const valuesByAggregation: Record<string, unknown[]> = {};\n\n            valuesByGroupKey.forEach((value) => {\n              const fieldWithValuesForGroup = value[fieldName];\n              const results = reduceField({\n                field: fieldWithValuesForGroup,\n                reducers: aggregations,\n              });\n\n              for (const aggregation of aggregations) {\n                if (!Array.isArray(valuesByAggregation[aggregation])) {\n                  valuesByAggregation[aggregation] = [];\n                }\n                valuesByAggregation[aggregation].push(results[aggregation]);\n              }\n            });\n\n            for (const aggregation of aggregations) {\n              const aggregationField: Field = {\n                name: `${fieldName} (${aggregation})`,\n                values: valuesByAggregation[aggregation] ?? [],\n                type: getFieldTypeForReducer(aggregation, field.type),\n                config: {},\n              };\n\n              fields.push(aggregationField);\n            }\n          }\n\n          processed.push({\n            refId: frame.refId,\n            fields,\n            length: valuesByGroupKey.size,\n          });\n        }\n\n        return processed;\n      })\n    ),\n};\n\n// exported for test\nexport const shouldCalculateField = (field: Field, options: GroupByTransformerOptions): boolean => {\n  const fieldName = getFieldDisplayName(field);\n  const { operation, aggregations = [] } = options.fields[fieldName] ?? {};\n\n  if (!Array.isArray(aggregations)) {\n    return false;\n  } else if (operation === GroupByOperationID.aggregate) {\n    return aggregations.length > 0;\n  } else if (operation === GroupByOperationID.groupBy) {\n    return aggregations.length === 1 && aggregations[0] === ReducerID.count;\n  } else {\n    return false;\n  }\n};\n\n/**\n * Groups values together by key. This will create a mapping of strings\n * to _FieldMaps_ that will then be used to group values on.\n *\n * @param frame\n *  The dataframe containing the data to group.\n * @param groupByFields\n *  An array of fields to group on.\n */\nexport function groupValuesByKey(frame: DataFrame, groupByFields: Field[]) {\n  const valuesByGroupKey = new Map<string, FieldMap>();\n\n  for (let rowIndex = 0; rowIndex < frame.length; rowIndex++) {\n    const groupKey = String(groupByFields.map((field) => field.values[rowIndex]));\n    const valuesByField = valuesByGroupKey.get(groupKey) ?? {};\n\n    if (!valuesByGroupKey.has(groupKey)) {\n      valuesByGroupKey.set(groupKey, valuesByField);\n    }\n\n    for (let field of frame.fields) {\n      const fieldName = getFieldDisplayName(field);\n\n      if (!valuesByField[fieldName]) {\n        valuesByField[fieldName] = {\n          name: fieldName,\n          type: field.type,\n          config: { ...field.config },\n          values: [],\n        };\n      }\n\n      valuesByField[fieldName].values.push(field.values[rowIndex]);\n    }\n  }\n\n  return valuesByGroupKey;\n}\n\n/**\n * Create new fields which will be used to display grouped values.\n *\n * @param groupByFields\n * @param valuesByGroupKey\n * @returns\n *  Returns an array of fields that have been grouped.\n */\nexport function createGroupedFields(groupByFields: Field[], valuesByGroupKey: Map<string, FieldMap>): Field[] {\n  const fields: Field[] = [];\n\n  for (const field of groupByFields) {\n    const values: unknown[] = [];\n    const fieldName = getFieldDisplayName(field);\n\n    valuesByGroupKey.forEach((value) => {\n      values.push(value[fieldName].values[0]);\n    });\n\n    fields.push({\n      name: field.name,\n      type: field.type,\n      config: {\n        ...field.config,\n      },\n      values,\n    });\n  }\n\n  return fields;\n}\n"],"names":["GroupByOperationID","DataTransformerID","findMaxFields","TransformationApplicabilityLevels","map","getFieldMatcher","FieldMatcherID","getFieldDisplayName","reduceField","getFieldTypeForReducer","ReducerID"],"mappings":"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"}