mirror of https://github.com/grafana/grafana
Feat: Feature toggle admin page frontend write UI and InteractiveTable sorting (#73533)
* updates * make save button always visible but disabled * only reset toggles if there isn't an error response * make linters happy * update post body to match backend * fix linter again * be smarter about sorting of empty descriptions * run prettier * fix payload * Re-add disabled to switch --------- Co-authored-by: Joao Calisto <joao.santana.calisto@gmail.com> Co-authored-by: Michael Mandrus <michael.mandrus@grafana.com>pull/74061/head
parent
025b2f3011
commit
2794b8628e
@ -1,35 +1,113 @@ |
|||||||
import React from 'react'; |
import React, { useState } from 'react'; |
||||||
|
|
||||||
import { Switch, InteractiveTable, type CellProps } from '@grafana/ui'; |
import { Switch, InteractiveTable, type CellProps, Button, type SortByFn } from '@grafana/ui'; |
||||||
|
|
||||||
import { type FeatureToggle } from './AdminFeatureTogglesAPI'; |
import { type FeatureToggle, useUpdateFeatureTogglesMutation } from './AdminFeatureTogglesAPI'; |
||||||
|
|
||||||
interface Props { |
interface Props { |
||||||
featureToggles: FeatureToggle[]; |
featureToggles: FeatureToggle[]; |
||||||
} |
} |
||||||
|
|
||||||
|
const sortByName: SortByFn<FeatureToggle> = (a, b) => { |
||||||
|
return a.original.name.localeCompare(b.original.name); |
||||||
|
}; |
||||||
|
|
||||||
|
const sortByDescription: SortByFn<FeatureToggle> = (a, b) => { |
||||||
|
if (!a.original.description && !b.original.description) { |
||||||
|
return 0; |
||||||
|
} else if (!a.original.description) { |
||||||
|
return 1; |
||||||
|
} else if (!b.original.description) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
return a.original.description.localeCompare(b.original.description); |
||||||
|
}; |
||||||
|
|
||||||
|
const sortByEnabled: SortByFn<FeatureToggle> = (a, b) => { |
||||||
|
return a.original.enabled === b.original.enabled ? 0 : a.original.enabled ? 1 : -1; |
||||||
|
}; |
||||||
|
|
||||||
export function AdminFeatureTogglesTable({ featureToggles }: Props) { |
export function AdminFeatureTogglesTable({ featureToggles }: Props) { |
||||||
|
const [localToggles, setLocalToggles] = useState<FeatureToggle[]>(featureToggles); |
||||||
|
const [updateFeatureToggles] = useUpdateFeatureTogglesMutation(); |
||||||
|
const [modifiedToggles, setModifiedToggles] = useState<FeatureToggle[]>([]); |
||||||
|
|
||||||
|
const handleToggleChange = (toggle: FeatureToggle, newValue: boolean) => { |
||||||
|
const updatedToggle = { ...toggle, enabled: newValue }; |
||||||
|
|
||||||
|
// Update the local state
|
||||||
|
const updatedToggles = localToggles.map((t) => (t.name === toggle.name ? updatedToggle : t)); |
||||||
|
setLocalToggles(updatedToggles); |
||||||
|
|
||||||
|
// Check if the toggle exists in modifiedToggles
|
||||||
|
const existingToggle = modifiedToggles.find((t) => t.name === toggle.name); |
||||||
|
|
||||||
|
// If it exists and its state is the same as the updated one, remove it from modifiedToggles
|
||||||
|
if (existingToggle && existingToggle.enabled === newValue) { |
||||||
|
setModifiedToggles((prev) => prev.filter((t) => t.name !== toggle.name)); |
||||||
|
} else { |
||||||
|
// Else, add/update the toggle in modifiedToggles
|
||||||
|
setModifiedToggles((prev) => { |
||||||
|
const newToggles = prev.filter((t) => t.name !== toggle.name); |
||||||
|
newToggles.push(updatedToggle); |
||||||
|
return newToggles; |
||||||
|
}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSaveChanges = async () => { |
||||||
|
const resp = await updateFeatureToggles(modifiedToggles); |
||||||
|
// Reset modifiedToggles after successful update
|
||||||
|
if (!('error' in resp)) { |
||||||
|
setModifiedToggles([]); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const hasModifications = () => { |
||||||
|
// Check if there are any differences between the original toggles and the local toggles
|
||||||
|
return featureToggles.some((originalToggle) => { |
||||||
|
const modifiedToggle = localToggles.find((t) => t.name === originalToggle.name); |
||||||
|
return modifiedToggle && modifiedToggle.enabled !== originalToggle.enabled; |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
const columns = [ |
const columns = [ |
||||||
{ |
{ |
||||||
id: 'name', |
id: 'name', |
||||||
header: 'Name', |
header: 'Name', |
||||||
cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>, |
cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>, |
||||||
|
sortType: sortByName, |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
id: 'description', |
id: 'description', |
||||||
header: 'Description', |
header: 'Description', |
||||||
cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>, |
cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>, |
||||||
|
sortType: sortByDescription, |
||||||
}, |
}, |
||||||
{ |
{ |
||||||
id: 'enabled', |
id: 'enabled', |
||||||
header: 'State', |
header: 'State', |
||||||
cell: ({ cell: { value } }: CellProps<FeatureToggle, boolean>) => ( |
cell: ({ row }: CellProps<FeatureToggle, boolean>) => ( |
||||||
<div> |
<div> |
||||||
<Switch value={value} disabled={true} /> |
<Switch |
||||||
|
value={row.original.enabled} |
||||||
|
disabled={row.original.readOnly} |
||||||
|
onChange={(e) => handleToggleChange(row.original, e.currentTarget.checked)} |
||||||
|
/> |
||||||
</div> |
</div> |
||||||
), |
), |
||||||
|
sortType: sortByEnabled, |
||||||
}, |
}, |
||||||
]; |
]; |
||||||
|
|
||||||
return <InteractiveTable columns={columns} data={featureToggles} getRowId={(featureToggle) => featureToggle.name} />; |
return ( |
||||||
|
<> |
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end', padding: '0 0 5px 0' }}> |
||||||
|
<Button disabled={!hasModifications()} onClick={handleSaveChanges}> |
||||||
|
Save Changes |
||||||
|
</Button> |
||||||
|
</div> |
||||||
|
<InteractiveTable columns={columns} data={localToggles} getRowId={(featureToggle) => featureToggle.name} /> |
||||||
|
</> |
||||||
|
); |
||||||
} |
} |
||||||
|
|||||||
Loading…
Reference in new issue