{"version":3,"file":"AdminFeatureTogglesPage.22157fe31f358f1c4f09.js","mappings":"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","sources":["webpack://grafana/./public/app/features/admin/AdminFeatureTogglesAPI.ts","webpack://grafana/./public/app/features/admin/AdminFeatureTogglesTable.tsx","webpack://grafana/./public/app/features/admin/AdminFeatureTogglesPage.tsx"],"sourcesContent":["import { getBackendSrv } from '@grafana/runtime';\n\nexport type FeatureToggle = {\n  name: string;\n  description?: string;\n  enabled: boolean;\n  stage: string;\n  readOnly?: boolean;\n  hidden?: boolean;\n};\n\nexport type CurrentTogglesState = {\n  restartRequired: boolean;\n  allowEditing: boolean;\n  toggles: FeatureToggle[];\n};\n\ninterface ResolvedToggleState {\n  kind: 'ResolvedToggleState';\n  restartRequired?: boolean;\n  allowEditing?: boolean;\n  toggles?: K8sToggleSpec[]; // not used in patch\n  enabled: { [key: string]: boolean };\n}\n\ninterface K8sToggleSpec {\n  name: string;\n  description: string;\n  enabled: boolean;\n  writeable: boolean;\n  source: K8sToggleSource;\n  stage: string;\n}\n\ninterface K8sToggleSource {\n  namespace: string;\n  name: string;\n}\n\ninterface FeatureTogglesAPI {\n  getFeatureToggles(): Promise<CurrentTogglesState>;\n  updateFeatureToggles(toggles: FeatureToggle[]): Promise<void>;\n}\n\nclass K8sAPI implements FeatureTogglesAPI {\n  baseURL = '/apis/featuretoggle.grafana.app/v0alpha1';\n\n  async getFeatureToggles(): Promise<CurrentTogglesState> {\n    const current = await getBackendSrv().get<ResolvedToggleState>(this.baseURL + '/current');\n    return {\n      restartRequired: Boolean(current.restartRequired),\n      allowEditing: Boolean(current.allowEditing),\n      toggles: current.toggles!.map((t) => ({\n        name: t.name,\n        description: t.description!,\n        enabled: t.enabled,\n        readOnly: !Boolean(t.writeable),\n        stage: t.stage,\n        hidden: false, // only return visible things\n      })),\n    };\n  }\n  updateFeatureToggles(toggles: FeatureToggle[]): Promise<void> {\n    const patchBody: ResolvedToggleState = {\n      kind: 'ResolvedToggleState',\n      enabled: {},\n    };\n    toggles.forEach((t) => {\n      patchBody.enabled[t.name] = t.enabled;\n    });\n    return getBackendSrv().patch(this.baseURL + '/current', patchBody);\n  }\n}\n\nexport const getTogglesAPI = (): FeatureTogglesAPI => {\n  return new K8sAPI();\n};\n","import React, { useState, useRef } from 'react';\n\nimport { Switch, InteractiveTable, Tooltip, type CellProps, Button, ConfirmModal, type SortByFn } from '@grafana/ui';\n\nimport { FeatureToggle, getTogglesAPI } from './AdminFeatureTogglesAPI';\n\ninterface Props {\n  featureToggles: FeatureToggle[];\n  allowEditing: boolean;\n  onUpdateSuccess: () => void;\n}\n\nconst sortByName: SortByFn<FeatureToggle> = (a, b) => {\n  return a.original.name.localeCompare(b.original.name);\n};\n\nconst sortByDescription: SortByFn<FeatureToggle> = (a, b) => {\n  if (!a.original.description && !b.original.description) {\n    return 0;\n  } else if (!a.original.description) {\n    return 1;\n  } else if (!b.original.description) {\n    return -1;\n  }\n  return a.original.description.localeCompare(b.original.description);\n};\n\nconst sortByEnabled: SortByFn<FeatureToggle> = (a, b) => {\n  return a.original.enabled === b.original.enabled ? 0 : a.original.enabled ? 1 : -1;\n};\n\nexport function AdminFeatureTogglesTable({ featureToggles, allowEditing, onUpdateSuccess }: Props) {\n  // sort manually, doesn't look like it can be automatically done in the table\n  featureToggles.sort((a, b) => a.name.localeCompare(b.name));\n  const serverToggles = useRef<FeatureToggle[]>(featureToggles);\n  const [localToggles, setLocalToggles] = useState<FeatureToggle[]>(featureToggles);\n  const [isSaving, setIsSaving] = useState(false);\n  const [showSaveModel, setShowSaveModal] = useState(false);\n  const togglesApi = getTogglesAPI();\n\n  const handleToggleChange = (toggle: FeatureToggle, newValue: boolean) => {\n    const updatedToggle = { ...toggle, enabled: newValue };\n\n    // Update the local state\n    const updatedToggles = localToggles.map((t) => (t.name === toggle.name ? updatedToggle : t));\n    setLocalToggles(updatedToggles);\n  };\n\n  const handleSaveChanges = async () => {\n    setIsSaving(true);\n    try {\n      const modifiedToggles = getModifiedToggles();\n      await togglesApi.updateFeatureToggles(modifiedToggles);\n      // Pretend the values came from a new request\n      serverToggles.current = [...localToggles];\n      onUpdateSuccess(); // should trigger a new get\n    } finally {\n      setIsSaving(false);\n    }\n  };\n\n  const saveButtonRef = useRef<HTMLButtonElement | null>(null);\n  const showSaveChangesModal = (show: boolean) => () => {\n    setShowSaveModal(show);\n    if (!show && saveButtonRef.current) {\n      saveButtonRef.current.focus();\n    }\n  };\n\n  const getModifiedToggles = (): FeatureToggle[] => {\n    return localToggles.filter((toggle, index) => toggle.enabled !== serverToggles.current[index].enabled);\n  };\n\n  const hasModifications = () => {\n    // Check if there are any differences between the original toggles and the local toggles\n    return localToggles.some((toggle, index) => toggle.enabled !== serverToggles.current[index].enabled);\n  };\n\n  const getToggleTooltipContent = (readOnlyToggle?: boolean) => {\n    if (!allowEditing) {\n      return 'Feature management is not configured for editing';\n    }\n    if (readOnlyToggle) {\n      return 'This is a non-editable feature';\n    }\n    return '';\n  };\n\n  const getStageCell = (stage: string) => {\n    switch (stage) {\n      case 'GA':\n        return (\n          <Tooltip content={'General availability'}>\n            <div>GA</div>\n          </Tooltip>\n        );\n      case 'privatePreview':\n      case 'preview':\n      case 'experimental':\n        return 'Beta';\n      case 'deprecated':\n        return 'Deprecated';\n      default:\n        return stage;\n    }\n  };\n\n  const columns = [\n    {\n      id: 'name',\n      header: 'Name',\n      cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>,\n      sortType: sortByName,\n    },\n    {\n      id: 'description',\n      header: 'Description',\n      cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>,\n      sortType: sortByDescription,\n    },\n    {\n      id: 'stage',\n      header: 'Stage',\n      cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{getStageCell(value)}</div>,\n    },\n    {\n      id: 'enabled',\n      header: 'State',\n      cell: ({ row }: CellProps<FeatureToggle, boolean>) => {\n        const renderStateSwitch = (\n          <div>\n            <Switch\n              value={row.original.enabled}\n              disabled={row.original.readOnly}\n              onChange={(e) => handleToggleChange(row.original, e.currentTarget.checked)}\n              transparent={row.original.readOnly}\n            />\n          </div>\n        );\n\n        return row.original.readOnly ? (\n          <Tooltip content={getToggleTooltipContent(row.original.readOnly)}>{renderStateSwitch}</Tooltip>\n        ) : (\n          renderStateSwitch\n        );\n      },\n      sortType: sortByEnabled,\n    },\n  ];\n\n  return (\n    <>\n      {allowEditing && (\n        <div style={{ display: 'flex', justifyContent: 'flex-end', padding: '0 0 5px 0' }}>\n          <Button disabled={!hasModifications() || isSaving} onClick={showSaveChangesModal(true)} ref={saveButtonRef}>\n            {isSaving ? 'Saving...' : 'Save Changes'}\n          </Button>\n          <ConfirmModal\n            isOpen={showSaveModel}\n            title=\"Apply feature toggle changes\"\n            body={\n              <div>\n                <p>\n                  Some features are stable (GA) and enabled by default, whereas some are currently in their preliminary\n                  Beta phase, available for early adoption.\n                </p>\n                <p>We advise understanding the implications of each feature change before making modifications.</p>\n              </div>\n            }\n            confirmText=\"Save changes\"\n            onConfirm={async () => {\n              showSaveChangesModal(false)();\n              handleSaveChanges();\n            }}\n            onDismiss={showSaveChangesModal(false)}\n          />\n        </div>\n      )}\n      <InteractiveTable columns={columns} data={localToggles} getRowId={(featureToggle) => featureToggle.name} />\n    </>\n  );\n}\n","import { css } from '@emotion/css';\nimport React, { useState } from 'react';\nimport { useAsync } from 'react-use';\n\nimport { GrafanaTheme2 } from '@grafana/data';\nimport { useStyles2, Icon } from '@grafana/ui';\nimport { Page } from 'app/core/components/Page/Page';\n\nimport { getTogglesAPI } from './AdminFeatureTogglesAPI';\nimport { AdminFeatureTogglesTable } from './AdminFeatureTogglesTable';\n\nexport default function AdminFeatureTogglesPage() {\n  const [reload, setReload] = useState(1);\n  const togglesApi = getTogglesAPI();\n  const featureState = useAsync(() => togglesApi.getFeatureToggles(), [reload]);\n  const styles = useStyles2(getStyles);\n\n  const handleUpdateSuccess = () => {\n    setReload(reload + 1);\n  };\n\n  const EditingAlert = () => {\n    return (\n      <div className={styles.warning}>\n        <div className={styles.icon}>\n          <Icon name=\"exclamation-triangle\" />\n        </div>\n        <span className={styles.message}>\n          {featureState.value?.restartRequired\n            ? 'A restart is pending for your Grafana instance to apply the latest feature toggle changes'\n            : 'Saving feature toggle changes will prompt a restart of the instance, which may take a few minutes'}\n        </span>\n      </div>\n    );\n  };\n\n  const subTitle = (\n    <div>\n      View and edit feature toggles. Read more about feature toggles at{' '}\n      <a\n        className=\"external-link\"\n        target=\"_new\"\n        href=\"https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/\"\n      >\n        grafana.com\n      </a>\n      .\n    </div>\n  );\n\n  return (\n    <Page navId=\"feature-toggles\" subTitle={subTitle}>\n      <Page.Contents isLoading={featureState.loading}>\n        <>\n          {featureState.error}\n          {featureState.loading && 'Fetching feature toggles'}\n\n          <EditingAlert />\n          {featureState.value && (\n            <AdminFeatureTogglesTable\n              featureToggles={featureState.value.toggles}\n              allowEditing={featureState.value.allowEditing || false}\n              onUpdateSuccess={handleUpdateSuccess}\n            />\n          )}\n        </>\n      </Page.Contents>\n    </Page>\n  );\n}\n\nfunction getStyles(theme: GrafanaTheme2) {\n  return {\n    warning: css({\n      display: 'flex',\n      marginTop: theme.spacing(0.25),\n      marginBottom: theme.spacing(0.25),\n    }),\n    icon: css({\n      color: theme.colors.warning.main,\n      paddingRight: theme.spacing(),\n    }),\n    message: css({\n      color: theme.colors.text.secondary,\n      marginTop: theme.spacing(0.25),\n    }),\n  };\n}\n"],"names":["K8sAPI","current","t","toggles","patchBody","getTogglesAPI","sortByName","a","b","sortByDescription","sortByEnabled","AdminFeatureTogglesTable","featureToggles","allowEditing","onUpdateSuccess","serverToggles","localToggles","setLocalToggles","isSaving","setIsSaving","showSaveModel","setShowSaveModal","togglesApi","handleToggleChange","toggle","newValue","updatedToggle","updatedToggles","handleSaveChanges","modifiedToggles","getModifiedToggles","saveButtonRef","showSaveChangesModal","show","index","hasModifications","getToggleTooltipContent","readOnlyToggle","getStageCell","stage","Tooltip","columns","value","row","renderStateSwitch","Switch","e","Button","ConfirmModal","InteractiveTable","featureToggle","AdminFeatureTogglesPage","reload","setReload","featureState","useAsync","styles","getStyles","handleUpdateSuccess","EditingAlert","Icon","subTitle","Page","theme"],"sourceRoot":""}