{"version":3,"file":"groupToNestedTable.cjs","sources":["../../../../src/transformations/transformers/groupToNestedTable.ts"],"sourcesContent":["import { map } from 'rxjs/operators';\n\nimport { guessFieldTypeForField } from '../../dataframe/processDataFrame';\nimport { getFieldDisplayName } from '../../field/fieldState';\nimport { DataFrame, Field, FieldType } from '../../types/dataFrame';\nimport { DataTransformerInfo, TransformationApplicabilityLevels } from '../../types/transformations';\nimport { ReducerID, reduceField } from '../fieldReducer';\n\nimport { GroupByFieldOptions, createGroupedFields, groupValuesByKey } from './groupBy';\nimport { DataTransformerID } from './ids';\nimport { findMaxFields } from './utils';\n\nexport const SHOW_NESTED_HEADERS_DEFAULT = true;\nconst MINIMUM_FIELDS_REQUIRED = 2;\n\nenum GroupByOperationID {\n  aggregate = 'aggregate',\n  groupBy = 'groupby',\n}\n\nexport interface GroupToNestedTableTransformerOptions {\n  showSubframeHeaders?: boolean;\n  fields: Record<string, GroupByFieldOptions>;\n}\n\ninterface FieldMap {\n  [key: string]: Field;\n}\n\nexport const groupToNestedTable: DataTransformerInfo<GroupToNestedTableTransformerOptions> = {\n  id: DataTransformerID.groupToNestedTable,\n  name: 'Group to nested tables',\n  description: 'Group data by a field value and create nested tables with the grouped data',\n  defaultOptions: {\n    showSubframeHeaders: SHOW_NESTED_HEADERS_DEFAULT,\n    fields: {},\n  },\n  isApplicable: (data) => {\n    // Group to nested table needs at least two fields\n    // a field to group on and to show in the nested table\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 to nested table 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 hasValidConfig = Object.keys(options.fields).find(\n          (name) => options.fields[name].operation === GroupByOperationID.groupBy\n        );\n        if (!hasValidConfig) {\n          return data;\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          const groupByFields: Field[] = frame.fields.filter((field) => shouldGroupOnField(field, options));\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          // Group data into sub frames so they will display as tables\n          const subFrames: DataFrame[][] = groupToSubframes(valuesByGroupKey, options);\n\n          // Then for each calculations configured, compute and add a new field (column)\n          for (let i = 0; i < frame.fields.length; i++) {\n            const field = frame.fields[i];\n\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: FieldType.other,\n                config: {},\n              };\n\n              aggregationField.type = detectFieldType(aggregation, field, aggregationField);\n              fields.push(aggregationField);\n            }\n          }\n\n          fields.push({\n            config: {},\n            name: '__nestedFrames',\n            type: FieldType.nestedFrames,\n            values: subFrames,\n          });\n\n          processed.push({\n            fields,\n            length: valuesByGroupKey.size,\n          });\n        }\n\n        return processed;\n      })\n    ),\n};\n\n/**\n * Given the appropriate data, create a sub-frame\n * which can then be displayed in a sub-table.\n */\nfunction createSubframe(fields: Field[], frameLength: number, options: GroupToNestedTableTransformerOptions) {\n  const showHeaders =\n    options.showSubframeHeaders === undefined ? SHOW_NESTED_HEADERS_DEFAULT : options.showSubframeHeaders;\n\n  return {\n    meta: { custom: { noHeader: !showHeaders } },\n    length: frameLength,\n    fields,\n  };\n}\n\n/**\n * Determines whether a field should be grouped on.\n *\n * @returns boolean\n *  This will return _true_ if a field should be grouped on and _false_ if it should not.\n */\nconst shouldGroupOnField = (field: Field, options: GroupToNestedTableTransformerOptions): boolean => {\n  const fieldName = getFieldDisplayName(field);\n  return options?.fields[fieldName]?.operation === GroupByOperationID.groupBy;\n};\n\n/**\n * Determines whether field aggregations should be calculated\n * @returns boolean\n *  This will return _true_ if a field should be calculated and _false_ if it should not.\n */\nconst shouldCalculateField = (field: Field, options: GroupToNestedTableTransformerOptions): boolean => {\n  const fieldName = getFieldDisplayName(field);\n  return (\n    options?.fields[fieldName]?.operation === GroupByOperationID.aggregate &&\n    Array.isArray(options?.fields[fieldName].aggregations) &&\n    options?.fields[fieldName].aggregations.length > 0\n  );\n};\n\n/**\n * Detect the type of field given the relevant aggregation.\n */\nconst detectFieldType = (aggregation: string, sourceField: Field, targetField: Field): FieldType => {\n  switch (aggregation) {\n    case ReducerID.allIsNull:\n      return FieldType.boolean;\n    case ReducerID.last:\n    case ReducerID.lastNotNull:\n    case ReducerID.first:\n    case ReducerID.firstNotNull:\n      return sourceField.type;\n    default:\n      return guessFieldTypeForField(targetField) ?? FieldType.string;\n  }\n};\n\n/**\n * Group values into subframes so that they'll be displayed\n * inside of a subtable.\n *\n * @param valuesByGroupKey\n *  A mapping of group keys to their respective grouped values.\n * @param options\n *   Transformation options, which are used to find ungrouped/unaggregated fields.\n * @returns\n */\nfunction groupToSubframes(\n  valuesByGroupKey: Map<string, FieldMap>,\n  options: GroupToNestedTableTransformerOptions\n): DataFrame[][] {\n  const subFrames: DataFrame[][] = [];\n\n  // Construct a subframe of any fields\n  // that aren't being group on or reduced\n  for (const [, value] of valuesByGroupKey) {\n    const nestedFields: Field[] = [];\n\n    for (const [fieldName, field] of Object.entries(value)) {\n      const fieldOpts = options.fields[fieldName];\n\n      if (fieldOpts === undefined) {\n        nestedFields.push(field);\n      }\n      // Depending on the configuration form state all of the following are possible\n      else if (\n        fieldOpts.aggregations === undefined ||\n        (fieldOpts.operation === GroupByOperationID.aggregate && fieldOpts.aggregations.length === 0) ||\n        fieldOpts.operation === null ||\n        fieldOpts.operation === undefined\n      ) {\n        nestedFields.push(field);\n      }\n    }\n\n    // If there are any values in the subfields\n    // push a new subframe with the fields\n    // otherwise push an empty frame\n    if (nestedFields.length > 0) {\n      subFrames.push([createSubframe(nestedFields, nestedFields[0].values.length, options)]);\n    } else {\n      subFrames.push([createSubframe([], 0, options)]);\n    }\n  }\n\n  return subFrames;\n}\n"],"names":["GroupByOperationID","DataTransformerID","findMaxFields","TransformationApplicabilityLevels","map","groupValuesByKey","createGroupedFields","getFieldDisplayName","reduceField","FieldType","ReducerID","guessFieldTypeForField"],"mappings":"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"}