{"version":3,"file":"OperationParamEditorRegistry.mjs","sources":["../../../../src/querybuilder/shared/OperationParamEditorRegistry.tsx"],"sourcesContent":["// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/querybuilder/shared/OperationParamEditor.tsx\nimport { css } from '@emotion/css';\nimport { type ComponentType } from 'react';\n\nimport { type GrafanaTheme2, type SelectableValue, toOption } from '@grafana/data';\nimport { t } from '@grafana/i18n';\nimport { AutoSizeInput, Button, Checkbox, Select, useStyles2, Stack } from '@grafana/ui';\n\nimport { LabelParamEditor } from '../components/LabelParamEditor';\n\nimport { getOperationParamId } from './param_utils';\nimport { type QueryBuilderOperationParamDef, type QueryBuilderOperationParamEditorProps } from './types';\n\n/**\n * Registry of operation parameter editors that can be referenced by key.\n *\n * This approach solves a circular dependency problem in the codebase:\n * - Operation definitions need to reference editors (e.g., LabelParamEditor)\n * - Editors need to reference the modeller instance\n * - The modeller instance needs to reference operation definitions\n *\n * By using string keys instead of direct imports, we break this cycle:\n * 1. Operation definitions reference editors by key (no component import needed)\n * 2. The registry maps these keys to actual editor components\n * 3. The wrapper component (OperationParamEditorWrapper) injects the modeller instance\n *\n * This creates a clear dependency flow:\n * Operation Definitions -> Registry -> Editor Components <- Wrapper <- Modeller Instance\n *\n * @example\n * ```ts\n * {\n *   id: 'someOperation',\n *   params: [{\n *     name: 'Label',\n *     type: 'string',\n *     editor: 'LabelParamEditor' // Reference by key instead of supplying the component directly\n *   }]\n * }\n * ```\n */\nconst editorMap: Record<string, ComponentType<QueryBuilderOperationParamEditorProps>> = {\n  // The wrapper component will ensure the modeller is provided\n  LabelParamEditor: LabelParamEditor as ComponentType<QueryBuilderOperationParamEditorProps>,\n};\n\n/**\n * Resolves an operation parameter editor based on the parameter definition.\n *\n * The editor can be specified in three ways:\n * 1. As a string key referencing a registered editor in editorMap\n * 2. As a direct component reference\n * 3. Based on the parameter type (string, number, boolean) or options\n *\n * This flexibility allows operation definitions to be decoupled from editor implementations\n * while maintaining type safety and clear dependencies.\n */\nexport function getOperationParamEditor(\n  paramDef: QueryBuilderOperationParamDef\n): ComponentType<QueryBuilderOperationParamEditorProps> {\n  if (paramDef.editor) {\n    if (typeof paramDef.editor === 'string') {\n      return editorMap[paramDef.editor] || SimpleInputParamEditor;\n    }\n    return paramDef.editor;\n  }\n\n  if (paramDef.options) {\n    return SelectInputParamEditor;\n  }\n\n  switch (paramDef.type) {\n    case 'boolean':\n      return BoolInputParamEditor;\n    case 'number':\n    case 'string':\n    default:\n      return SimpleInputParamEditor;\n  }\n}\n\nfunction SimpleInputParamEditor(props: QueryBuilderOperationParamEditorProps) {\n  return (\n    <AutoSizeInput\n      id={getOperationParamId(props.operationId, props.index)}\n      defaultValue={props.value?.toString()}\n      minWidth={props.paramDef.minWidth}\n      placeholder={props.paramDef.placeholder}\n      title={props.paramDef.description}\n      maxWidth={(props.paramDef.minWidth || 20) * 3}\n      onCommitChange={(evt) => {\n        props.onChange(props.index, evt.currentTarget.value);\n        if (props.paramDef.runQueryOnEnter && evt.type === 'keydown') {\n          props.onRunQuery();\n        }\n      }}\n    />\n  );\n}\n\nfunction BoolInputParamEditor(props: QueryBuilderOperationParamEditorProps) {\n  return (\n    <Checkbox\n      id={getOperationParamId(props.operationId, props.index)}\n      value={Boolean(props.value)}\n      onChange={(evt) => props.onChange(props.index, evt.currentTarget.checked)}\n    />\n  );\n}\n\nfunction SelectInputParamEditor({\n  paramDef,\n  value,\n  index,\n  operationId,\n  onChange,\n}: QueryBuilderOperationParamEditorProps) {\n  const styles = useStyles2(getStyles);\n  let selectOptions = paramDef.options as SelectableValue[];\n\n  if (!selectOptions[0]?.label) {\n    selectOptions = paramDef.options!.map((option) => ({\n      label: option.toString(),\n      value: option,\n    }));\n  }\n\n  let valueOption = selectOptions.find((x) => x.value === value) ?? toOption(value as string);\n\n  // If we have optional options param and don't have value, we want to render button with which we add optional options.\n  // This makes it easier to understand what needs to be selected and what is optional.\n  if (!value && paramDef.optional) {\n    return (\n      <div className={styles.optionalParam}>\n        <Button\n          size=\"sm\"\n          variant=\"secondary\"\n          title={t('grafana-prometheus.querybuilder.operation-param-editor.title-add', 'Add {{name}}', {\n            name: paramDef.name,\n          })}\n          icon=\"plus\"\n          onClick={() => onChange(index, selectOptions[0].value)}\n        >\n          {paramDef.name}\n        </Button>\n      </div>\n    );\n  }\n\n  return (\n    <Stack gap={0.5} direction=\"row\" alignItems=\"center\">\n      <Select\n        id={getOperationParamId(operationId, index)}\n        value={valueOption}\n        options={selectOptions}\n        placeholder={paramDef.placeholder}\n        allowCustomValue={true}\n        onChange={(value) => onChange(index, value.value!)}\n        width={paramDef.minWidth || 'auto'}\n      />\n      {paramDef.optional && (\n        <Button\n          data-testid={`operations.${index}.remove-param`}\n          size=\"sm\"\n          fill=\"text\"\n          icon=\"times\"\n          variant=\"secondary\"\n          aria-label={t('grafana-prometheus.querybuilder.operation-param-editor.title-remove', 'Remove {{name}}', {\n            name: paramDef.name,\n          })}\n          onClick={() => onChange(index, '')}\n        />\n      )}\n    </Stack>\n  );\n}\n\nconst getStyles = (theme: GrafanaTheme2) => {\n  return {\n    optionalParam: css({\n      marginTop: theme.spacing(1),\n    }),\n  };\n};\n"],"names":["value"],"mappings":"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"}