mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: new variants table (#1025)
* fix: cleanup * fix: text * fix: stable references * refactor: fix VARIANT_WEIGH test id * refactor: fix variant element selection in e2e test * fix: update variants table * fix: refactor action cell Co-authored-by: olav <mail@olav.io>
This commit is contained in:
		
							parent
							
								
									f4d02e37b7
								
							
						
					
					
						commit
						7c52f0fcc8
					
				| @ -6,6 +6,8 @@ const ENTERPRISE = Boolean(Cypress.env('ENTERPRISE')); | ||||
| const randomId = String(Math.random()).split('.')[1]; | ||||
| const featureToggleName = `unleash-e2e-${randomId}`; | ||||
| const baseUrl = Cypress.config().baseUrl; | ||||
| const variant1 = 'variant1'; | ||||
| const variant2 = 'variant2'; | ||||
| let strategyId = ''; | ||||
| 
 | ||||
| // Disable the prod guard modal by marking it as seen.
 | ||||
| @ -233,9 +235,6 @@ describe('feature', () => { | ||||
|     }); | ||||
| 
 | ||||
|     it('can add two variant to the feature', () => { | ||||
|         const variantName = 'my-new-variant'; | ||||
|         const secondVariantName = 'my-second-variant'; | ||||
| 
 | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
| 
 | ||||
|         cy.intercept( | ||||
| @ -245,26 +244,26 @@ describe('feature', () => { | ||||
|                 if (req.body.length === 1) { | ||||
|                     expect(req.body[0].op).to.equal('add'); | ||||
|                     expect(req.body[0].path).to.match(/\//); | ||||
|                     expect(req.body[0].value.name).to.equal(variantName); | ||||
|                     expect(req.body[0].value.name).to.equal(variant1); | ||||
|                 } else if (req.body.length === 2) { | ||||
|                     expect(req.body[0].op).to.equal('replace'); | ||||
|                     expect(req.body[0].path).to.match(/weight/); | ||||
|                     expect(req.body[0].value).to.equal(500); | ||||
|                     expect(req.body[1].op).to.equal('add'); | ||||
|                     expect(req.body[1].path).to.match(/\//); | ||||
|                     expect(req.body[1].value.name).to.equal(secondVariantName); | ||||
|                     expect(req.body[1].value.name).to.equal(variant2); | ||||
|                 } | ||||
|             } | ||||
|         ).as('variantCreation'); | ||||
| 
 | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variantName); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant1); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantCreation'); | ||||
|         cy.get('[data-testid=ADD_VARIANT_BUTTON]').click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(secondVariantName); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]').type(variant2); | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantCreation'); | ||||
|     }); | ||||
| @ -272,7 +271,7 @@ describe('feature', () => { | ||||
|     it('can set weight to fixed value for one of the variants', () => { | ||||
|         cy.visit(`/projects/default/features/${featureToggleName}/variants`); | ||||
| 
 | ||||
|         cy.get('[data-testid=VARIANT_EDIT_BUTTON]').first().click(); | ||||
|         cy.get(`[data-testid=VARIANT_EDIT_BUTTON_${variant1}]`).click(); | ||||
|         cy.wait(1000); | ||||
|         cy.get('[data-testid=VARIANT_NAME_INPUT]') | ||||
|             .children() | ||||
| @ -299,9 +298,10 @@ describe('feature', () => { | ||||
| 
 | ||||
|         cy.get('[data-testid=DIALOGUE_CONFIRM_ID]').click(); | ||||
|         cy.wait('@variantUpdate'); | ||||
|         cy.get('[data-testid=VARIANT_WEIGHT]') | ||||
|             .first() | ||||
|             .should('have.text', '15 %'); | ||||
|         cy.get(`[data-testid=VARIANT_WEIGHT_${variant1}]`).should( | ||||
|             'have.text', | ||||
|             '15 %' | ||||
|         ); | ||||
|     }); | ||||
| 
 | ||||
|     it('can delete variant', () => { | ||||
|  | ||||
| @ -5,18 +5,22 @@ import { useStyles } from './TextCell.styles'; | ||||
| interface ITextCellProps { | ||||
|     value?: string | null; | ||||
|     lineClamp?: number; | ||||
|     'data-testid'?: string; | ||||
| } | ||||
| 
 | ||||
| export const TextCell: FC<ITextCellProps> = ({ | ||||
|     value, | ||||
|     children, | ||||
|     lineClamp, | ||||
|     'data-testid': testid, | ||||
| }) => { | ||||
|     const { classes } = useStyles({ lineClamp }); | ||||
| 
 | ||||
|     return ( | ||||
|         <Box className={classes.wrapper}> | ||||
|             <span data-loading>{children ?? value}</span> | ||||
|             <span data-loading="true" data-testid={testid}> | ||||
|                 {children ?? value} | ||||
|             </span> | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| @ -1,17 +1,10 @@ | ||||
| import { useStyles } from './FeatureVariants.styles'; | ||||
| import FeatureOverviewVariants from './FeatureVariantsList/FeatureVariantsList'; | ||||
| import { FeatureVariantsList } from './FeatureVariantsList/FeatureVariantsList'; | ||||
| import { usePageTitle } from 'hooks/usePageTitle'; | ||||
| 
 | ||||
| const FeatureVariants = () => { | ||||
|     usePageTitle('Variants'); | ||||
| 
 | ||||
|     const { classes: styles } = useStyles(); | ||||
| 
 | ||||
|     return ( | ||||
|         <div className={styles.container}> | ||||
|             <FeatureOverviewVariants /> | ||||
|         </div> | ||||
|     ); | ||||
|     return <FeatureVariantsList />; | ||||
| }; | ||||
| 
 | ||||
| export default FeatureVariants; | ||||
|  | ||||
| @ -1,21 +1,19 @@ | ||||
| import classnames from 'classnames'; | ||||
| import * as jsonpatch from 'fast-json-patch'; | ||||
| 
 | ||||
| import styles from './variants.module.scss'; | ||||
| import { | ||||
|     Alert, | ||||
|     Box, | ||||
|     Table, | ||||
|     TableBody, | ||||
|     TableCell, | ||||
|     TableHead, | ||||
|     TableRow, | ||||
|     Typography, | ||||
|     useMediaQuery, | ||||
| } from '@mui/material'; | ||||
| import { AddVariant } from './AddFeatureVariant/AddFeatureVariant'; | ||||
| 
 | ||||
| import { useContext, useEffect, useState } from 'react'; | ||||
| import { useContext, useEffect, useState, useMemo, useCallback } from 'react'; | ||||
| import { useFeature } from 'hooks/api/getters/useFeature/useFeature'; | ||||
| import AccessContext from 'contexts/AccessContext'; | ||||
| import FeatureVariantListItem from './FeatureVariantsListItem/FeatureVariantsListItem'; | ||||
| import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import useUnleashContext from 'hooks/api/getters/useUnleashContext/useUnleashContext'; | ||||
| @ -25,26 +23,43 @@ import useFeatureApi from 'hooks/api/actions/useFeatureApi/useFeatureApi'; | ||||
| import useToast from 'hooks/useToast'; | ||||
| import { updateWeight } from 'component/common/util'; | ||||
| import cloneDeep from 'lodash.clonedeep'; | ||||
| import useDeleteVariantMarkup from './FeatureVariantsListItem/useDeleteVariantMarkup'; | ||||
| import useDeleteVariantMarkup from './useDeleteVariantMarkup'; | ||||
| import PermissionButton from 'component/common/PermissionButton/PermissionButton'; | ||||
| import { formatUnknownError } from 'utils/formatUnknownError'; | ||||
| import { useRequiredPathParam } from 'hooks/useRequiredPathParam'; | ||||
| import { Edit, Delete } from '@mui/icons-material'; | ||||
| import { useTable, useSortBy, useGlobalFilter } from 'react-table'; | ||||
| import { PageContent } from 'component/common/PageContent/PageContent'; | ||||
| import { PageHeader } from 'component/common/PageHeader/PageHeader'; | ||||
| import { SortableTableHeader, TablePlaceholder } from 'component/common/Table'; | ||||
| import { sortTypes } from 'utils/sortTypes'; | ||||
| import { PayloadOverridesCell } from './PayloadOverridesCell/PayloadOverridesCell'; | ||||
| import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; | ||||
| import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | ||||
| import theme from 'themes/theme'; | ||||
| import { VariantsActionCell } from './VariantsActionsCell/VariantsActionsCell'; | ||||
| 
 | ||||
| const FeatureOverviewVariants = () => { | ||||
| export const FeatureVariantsList = () => { | ||||
|     const { hasAccess } = useContext(AccessContext); | ||||
|     const projectId = useRequiredPathParam('projectId'); | ||||
|     const featureId = useRequiredPathParam('featureId'); | ||||
|     const { feature, refetchFeature } = useFeature(projectId, featureId); | ||||
|     const { feature, refetchFeature, loading } = useFeature( | ||||
|         projectId, | ||||
|         featureId | ||||
|     ); | ||||
|     const [variants, setVariants] = useState<IFeatureVariant[]>([]); | ||||
|     const [editing, setEditing] = useState(false); | ||||
|     const { context } = useUnleashContext(); | ||||
|     const { setToastData, setToastApiError } = useToast(); | ||||
|     const { patchFeatureVariants } = useFeatureApi(); | ||||
|     const [editVariant, setEditVariant] = useState({}); | ||||
|     const [variantToEdit, setVariantToEdit] = useState({}); | ||||
|     const [showAddVariant, setShowAddVariant] = useState(false); | ||||
|     const [stickinessOptions, setStickinessOptions] = useState<string[]>([]); | ||||
|     const [delDialog, setDelDialog] = useState({ name: '', show: false }); | ||||
| 
 | ||||
|     const isMediumScreen = useMediaQuery(theme.breakpoints.down('md')); | ||||
|     const isLargeScreen = useMediaQuery(theme.breakpoints.down('lg')); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (feature) { | ||||
|             setClonedVariants(feature.variants); | ||||
| @ -63,6 +78,146 @@ const FeatureOverviewVariants = () => { | ||||
| 
 | ||||
|     const editable = hasAccess(UPDATE_FEATURE_VARIANTS, projectId); | ||||
| 
 | ||||
|     const data = useMemo(() => { | ||||
|         if (loading) { | ||||
|             return Array(5).fill({ | ||||
|                 name: 'Context name', | ||||
|                 description: 'Context description when loading', | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return feature.variants; | ||||
|     }, [feature.variants, loading]); | ||||
| 
 | ||||
|     const editVariant = useCallback( | ||||
|         (name: string) => { | ||||
|             const variant = { | ||||
|                 ...variants.find(variant => variant.name === name), | ||||
|             }; | ||||
|             setVariantToEdit(variant); | ||||
|             setEditing(true); | ||||
|             setShowAddVariant(true); | ||||
|         }, | ||||
|         [variants, setVariantToEdit, setEditing, setShowAddVariant] | ||||
|     ); | ||||
| 
 | ||||
|     const columns = useMemo( | ||||
|         () => [ | ||||
|             { | ||||
|                 Header: 'Name', | ||||
|                 accessor: 'name', | ||||
|                 width: '25%', | ||||
|                 Cell: ({ | ||||
|                     row: { | ||||
|                         original: { name }, | ||||
|                     }, | ||||
|                 }: any) => { | ||||
|                     return <TextCell data-loading>{name}</TextCell>; | ||||
|                 }, | ||||
|                 sortType: 'alphanumeric', | ||||
|             }, | ||||
|             { | ||||
|                 Header: 'Payload/Overrides', | ||||
|                 accessor: 'data', | ||||
|                 Cell: ({ | ||||
|                     row: { | ||||
|                         original: { overrides, payload }, | ||||
|                     }, | ||||
|                 }: any) => { | ||||
|                     return ( | ||||
|                         <PayloadOverridesCell | ||||
|                             overrides={overrides} | ||||
|                             payload={payload} | ||||
|                         /> | ||||
|                     ); | ||||
|                 }, | ||||
|                 disableSortBy: true, | ||||
|             }, | ||||
|             { | ||||
|                 Header: 'Weight', | ||||
|                 accessor: 'weight', | ||||
|                 width: '20%', | ||||
|                 Cell: ({ | ||||
|                     row: { | ||||
|                         original: { name, weight }, | ||||
|                     }, | ||||
|                 }: any) => { | ||||
|                     return ( | ||||
|                         <TextCell data-testid={`VARIANT_WEIGHT_${name}`}> | ||||
|                             {weight / 10.0} % | ||||
|                         </TextCell> | ||||
|                     ); | ||||
|                 }, | ||||
|                 sortType: 'number', | ||||
|             }, | ||||
|             { | ||||
|                 Header: 'Type', | ||||
|                 accessor: 'weightType', | ||||
|                 width: '20%', | ||||
|                 Cell: ({ | ||||
|                     row: { | ||||
|                         original: { weightType }, | ||||
|                     }, | ||||
|                 }: any) => { | ||||
|                     return <TextCell>{weightType}</TextCell>; | ||||
|                 }, | ||||
|                 sortType: 'alphanumeric', | ||||
|             }, | ||||
|             { | ||||
|                 Header: 'Actions', | ||||
|                 id: 'Actions', | ||||
|                 align: 'right', | ||||
|                 Cell: ({ row: { original } }: any) => ( | ||||
|                     <VariantsActionCell | ||||
|                         editVariant={editVariant} | ||||
|                         setDelDialog={setDelDialog} | ||||
|                         variant={original as IFeatureVariant} | ||||
|                         projectId={projectId} | ||||
|                     /> | ||||
|                 ), | ||||
|                 width: 150, | ||||
|                 disableSortBy: true, | ||||
|             }, | ||||
|         ], | ||||
|         [projectId, editVariant] | ||||
|     ); | ||||
| 
 | ||||
|     const initialState = useMemo( | ||||
|         () => ({ | ||||
|             sortBy: [{ id: 'name', desc: false }], | ||||
|         }), | ||||
|         [] | ||||
|     ); | ||||
| 
 | ||||
|     const { | ||||
|         getTableProps, | ||||
|         getTableBodyProps, | ||||
|         headerGroups, | ||||
|         rows, | ||||
|         prepareRow, | ||||
|         setHiddenColumns, | ||||
|     } = useTable( | ||||
|         { | ||||
|             columns: columns as any[], | ||||
|             data: data as any[], | ||||
|             initialState, | ||||
|             sortTypes, | ||||
|             autoResetGlobalFilter: false, | ||||
|             autoResetSortBy: false, | ||||
|             disableSortRemove: true, | ||||
|         }, | ||||
|         useGlobalFilter, | ||||
|         useSortBy | ||||
|     ); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         if (isMediumScreen) { | ||||
|             setHiddenColumns(['weightType', 'data']); | ||||
|         } else if (isLargeScreen) { | ||||
|             setHiddenColumns(['weightType']); | ||||
|         } | ||||
|     }, [setHiddenColumns, isMediumScreen, isLargeScreen]); | ||||
| 
 | ||||
|     // @ts-expect-error
 | ||||
|     const setClonedVariants = clonedVariants => | ||||
|         setVariants(cloneDeep(clonedVariants)); | ||||
| @ -70,26 +225,7 @@ const FeatureOverviewVariants = () => { | ||||
|     const handleCloseAddVariant = () => { | ||||
|         setShowAddVariant(false); | ||||
|         setEditing(false); | ||||
|         setEditVariant({}); | ||||
|     }; | ||||
| 
 | ||||
|     const renderVariants = () => { | ||||
|         return variants.map(variant => { | ||||
|             return ( | ||||
|                 <FeatureVariantListItem | ||||
|                     key={variant.name} | ||||
|                     variant={variant} | ||||
|                     editVariant={(name: string) => { | ||||
|                         const v = { ...variants.find(v => v.name === name) }; | ||||
|                         setEditVariant(v); | ||||
|                         setEditing(true); | ||||
|                         setShowAddVariant(true); | ||||
|                     }} | ||||
|                     setDelDialog={setDelDialog} | ||||
|                     editable={editable} | ||||
|                 /> | ||||
|             ); | ||||
|         }); | ||||
|         setVariantToEdit({}); | ||||
|     }; | ||||
| 
 | ||||
|     const renderStickiness = () => { | ||||
| @ -118,10 +254,7 @@ const FeatureOverviewVariants = () => { | ||||
|                     onChange={onChange} | ||||
|                 /> | ||||
|                    | ||||
|                 <small | ||||
|                     className={classnames(styles.paragraph, styles.helperText)} | ||||
|                     style={{ display: 'block', marginTop: '0.5rem' }} | ||||
|                 > | ||||
|                 <small style={{ display: 'block', marginTop: '0.5rem' }}> | ||||
|                     By overriding the stickiness you can control which parameter | ||||
|                     is used to ensure consistent traffic allocation across | ||||
|                     variants.{' '} | ||||
| @ -245,55 +378,82 @@ const FeatureOverviewVariants = () => { | ||||
|         return jsonpatch.compare(feature.variants, newVariants); | ||||
|     }; | ||||
| 
 | ||||
|     const addVariant = () => { | ||||
|         setEditing(false); | ||||
|         if (variants.length === 0) { | ||||
|             setVariantToEdit({ weight: 1000 }); | ||||
|         } else { | ||||
|             setVariantToEdit({ | ||||
|                 weightType: 'variable', | ||||
|             }); | ||||
|         } | ||||
|         setShowAddVariant(true); | ||||
|     }; | ||||
| 
 | ||||
|     return ( | ||||
|         <section style={{ padding: '16px' }}> | ||||
|             <Typography variant="body1"> | ||||
|         <PageContent | ||||
|             isLoading={loading} | ||||
|             header={ | ||||
|                 <PageHeader | ||||
|                     title="Variants" | ||||
|                     actions={ | ||||
|                         <> | ||||
|                             <PermissionButton | ||||
|                                 onClick={addVariant} | ||||
|                                 data-testid={'ADD_VARIANT_BUTTON'} | ||||
|                                 permission={UPDATE_FEATURE_VARIANTS} | ||||
|                                 projectId={projectId} | ||||
|                             > | ||||
|                                 New variant | ||||
|                             </PermissionButton> | ||||
|                         </> | ||||
|                     } | ||||
|                 /> | ||||
|             } | ||||
|         > | ||||
|             <Alert severity="info" sx={{ marginBottom: '1rem' }}> | ||||
|                 Variants allows you to return a variant object if the feature | ||||
|                 toggle is considered enabled for the current request. When using | ||||
|                 variants you should use the{' '} | ||||
|                 <code style={{ color: 'navy' }}>getVariant()</code> method in | ||||
|                 the Client SDK. | ||||
|             </Typography> | ||||
| 
 | ||||
|             <ConditionallyRender | ||||
|                 condition={variants?.length > 0} | ||||
|                 show={ | ||||
|                     <Table className={styles.variantTable}> | ||||
|                         <TableHead> | ||||
|                             <TableRow> | ||||
|                                 <TableCell>Variant name</TableCell> | ||||
|                                 <TableCell className={styles.labels} /> | ||||
|                                 <TableCell>Weight</TableCell> | ||||
|                                 <TableCell>Weight Type</TableCell> | ||||
|                                 <TableCell className={styles.actions} /> | ||||
|                 <code style={{ fontWeight: 'bold' }}>getVariant()</code> method | ||||
|                 in the Client SDK. | ||||
|             </Alert> | ||||
|             <Table {...getTableProps()}> | ||||
|                 <SortableTableHeader headerGroups={headerGroups} /> | ||||
|                 <TableBody {...getTableBodyProps()}> | ||||
|                     {rows.map(row => { | ||||
|                         prepareRow(row); | ||||
|                         return ( | ||||
|                             <TableRow | ||||
|                                 hover | ||||
|                                 {...row.getRowProps()} | ||||
|                                 style={{ height: '75px' }} | ||||
|                             > | ||||
|                                 {row.cells.map(cell => ( | ||||
|                                     <TableCell | ||||
|                                         {...cell.getCellProps()} | ||||
|                                         padding="none" | ||||
|                                     > | ||||
|                                         {cell.render('Cell')} | ||||
|                                     </TableCell> | ||||
|                                 ))} | ||||
|                             </TableRow> | ||||
|                         </TableHead> | ||||
|                         <TableBody>{renderVariants()}</TableBody> | ||||
|                     </Table> | ||||
|                         ); | ||||
|                     })} | ||||
|                 </TableBody> | ||||
|             </Table> | ||||
|             <ConditionallyRender | ||||
|                 condition={rows.length === 0} | ||||
|                 show={ | ||||
|                     <TablePlaceholder> | ||||
|                         No variants available. Get started by adding one. | ||||
|                     </TablePlaceholder> | ||||
|                 } | ||||
|                 elseShow={<p>No variants defined.</p>} | ||||
|             /> | ||||
| 
 | ||||
|             <br /> | ||||
| 
 | ||||
|             <div> | ||||
|                 <PermissionButton | ||||
|                     onClick={() => { | ||||
|                         setEditing(false); | ||||
|                         if (variants.length === 0) { | ||||
|                             setEditVariant({ weight: 1000 }); | ||||
|                         } else { | ||||
|                             setEditVariant({ weightType: 'variable' }); | ||||
|                         } | ||||
|                         setShowAddVariant(true); | ||||
|                     }} | ||||
|                     className={styles.addVariantButton} | ||||
|                     data-testid={'ADD_VARIANT_BUTTON'} | ||||
|                     permission={UPDATE_FEATURE_VARIANTS} | ||||
|                     projectId={projectId} | ||||
|                 > | ||||
|                     New variant | ||||
|                 </PermissionButton> | ||||
|                 <ConditionallyRender | ||||
|                     condition={editable} | ||||
|                     show={renderStickiness()} | ||||
| @ -314,13 +474,11 @@ const FeatureOverviewVariants = () => { | ||||
|                 validateName={validateName} | ||||
|                 validateWeight={validateWeight} | ||||
|                 // @ts-expect-error
 | ||||
|                 editVariant={editVariant} | ||||
|                 editVariant={variantToEdit} | ||||
|                 title={editing ? 'Edit variant' : 'Add variant'} | ||||
|             /> | ||||
| 
 | ||||
|             {delDialogueMarkup} | ||||
|         </section> | ||||
|         </PageContent> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default FeatureOverviewVariants; | ||||
|  | ||||
| @ -1,90 +0,0 @@ | ||||
| import { Chip, IconButton, TableCell, TableRow, Tooltip } from '@mui/material'; | ||||
| import { Delete, Edit } from '@mui/icons-material'; | ||||
| 
 | ||||
| import styles from '../variants.module.scss'; | ||||
| import { IFeatureVariant } from 'interfaces/featureToggle'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { weightTypes } from '../AddFeatureVariant/enums'; | ||||
| 
 | ||||
| interface IFeatureVariantListItem { | ||||
|     variant: IFeatureVariant; | ||||
|     editVariant: any; | ||||
|     setDelDialog: any; | ||||
|     editable: boolean; | ||||
| } | ||||
| 
 | ||||
| const FeatureVariantListItem = ({ | ||||
|     variant, | ||||
|     editVariant, | ||||
|     setDelDialog, | ||||
|     editable, | ||||
| }: IFeatureVariantListItem) => { | ||||
|     const { FIX } = weightTypes; | ||||
| 
 | ||||
|     return ( | ||||
|         <TableRow> | ||||
|             <TableCell data-testid={'VARIANT_NAME'}>{variant.name}</TableCell> | ||||
|             <TableCell className={styles.chipContainer}> | ||||
|                 <ConditionallyRender | ||||
|                     condition={Boolean(variant.payload)} | ||||
|                     show={<Chip label="Payload" />} | ||||
|                 /> | ||||
|                 <ConditionallyRender | ||||
|                     condition={ | ||||
|                         variant.overrides && variant.overrides.length > 0 | ||||
|                     } | ||||
|                     show={ | ||||
|                         <Chip | ||||
|                             style={{ | ||||
|                                 backgroundColor: 'rgba(173, 216, 230, 0.2)', | ||||
|                             }} | ||||
|                             label="Overrides" | ||||
|                         /> | ||||
|                     } | ||||
|                 /> | ||||
|             </TableCell> | ||||
|             <TableCell data-testid={'VARIANT_WEIGHT'}> | ||||
|                 {variant.weight / 10.0} % | ||||
|             </TableCell> | ||||
|             <TableCell data-testid={'VARIANT_WEIGHT_TYPE'}> | ||||
|                 {variant.weightType === FIX ? 'Fix' : 'Variable'} | ||||
|             </TableCell> | ||||
|             <ConditionallyRender | ||||
|                 condition={editable} | ||||
|                 show={ | ||||
|                     <TableCell className={styles.actions}> | ||||
|                         <div className={styles.actionsContainer}> | ||||
|                             <Tooltip title="Edit variant" arrow> | ||||
|                                 <IconButton | ||||
|                                     data-testid={'VARIANT_EDIT_BUTTON'} | ||||
|                                     onClick={() => editVariant(variant.name)} | ||||
|                                     size="large" | ||||
|                                 > | ||||
|                                     <Edit /> | ||||
|                                 </IconButton> | ||||
|                             </Tooltip> | ||||
|                             <Tooltip title="Delete variant" arrow> | ||||
|                                 <IconButton | ||||
|                                     data-testid={`VARIANT_DELETE_BUTTON_${variant.name}`} | ||||
|                                     onClick={e => { | ||||
|                                         e.stopPropagation(); | ||||
|                                         setDelDialog({ | ||||
|                                             show: true, | ||||
|                                             name: variant.name, | ||||
|                                         }); | ||||
|                                     }} | ||||
|                                     size="large" | ||||
|                                 > | ||||
|                                     <Delete /> | ||||
|                                 </IconButton> | ||||
|                             </Tooltip> | ||||
|                         </div> | ||||
|                     </TableCell> | ||||
|                 } | ||||
|                 elseShow={<TableCell className={styles.actions} />} | ||||
|             /> | ||||
|         </TableRow> | ||||
|     ); | ||||
| }; | ||||
| 
 | ||||
| export default FeatureVariantListItem; | ||||
| @ -0,0 +1,27 @@ | ||||
| import { Chip } from '@mui/material'; | ||||
| import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; | ||||
| import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; | ||||
| import { IOverride, IPayload } from 'interfaces/featureToggle'; | ||||
| 
 | ||||
| interface IPayloadOverridesCellProps { | ||||
|     payload: IPayload; | ||||
|     overrides: IOverride[]; | ||||
| } | ||||
| 
 | ||||
| export const PayloadOverridesCell = ({ | ||||
|     payload, | ||||
|     overrides, | ||||
| }: IPayloadOverridesCellProps) => { | ||||
|     return ( | ||||
|         <> | ||||
|             <ConditionallyRender | ||||
|                 condition={Boolean(payload)} | ||||
|                 show={<TextCell>Payload</TextCell>} | ||||
|             /> | ||||
|             <ConditionallyRender | ||||
|                 condition={overrides && overrides.length > 0} | ||||
|                 show={<TextCell>Overrides</TextCell>} | ||||
|             /> | ||||
|         </> | ||||
|     ); | ||||
| }; | ||||
| @ -0,0 +1,55 @@ | ||||
| import { Edit, Delete } from '@mui/icons-material'; | ||||
| import { Box } from '@mui/material'; | ||||
| import PermissionIconButton from 'component/common/PermissionIconButton/PermissionIconButton'; | ||||
| import { UPDATE_FEATURE_VARIANTS } from 'component/providers/AccessProvider/permissions'; | ||||
| import { IFeatureVariant } from 'interfaces/featureToggle'; | ||||
| 
 | ||||
| interface IVarintsActionCellProps { | ||||
|     projectId: string; | ||||
|     editVariant: (name: string) => void; | ||||
|     setDelDialog: React.Dispatch< | ||||
|         React.SetStateAction<{ | ||||
|             name: string; | ||||
|             show: boolean; | ||||
|         }> | ||||
|     >; | ||||
|     variant: IFeatureVariant; | ||||
| } | ||||
| 
 | ||||
| export const VariantsActionCell = ({ | ||||
|     projectId, | ||||
|     setDelDialog, | ||||
|     variant, | ||||
|     editVariant, | ||||
| }: IVarintsActionCellProps) => { | ||||
|     return ( | ||||
|         <Box | ||||
|             style={{ display: 'flex', justifyContent: 'flex-end' }} | ||||
|             data-loading | ||||
|         > | ||||
|             <PermissionIconButton | ||||
|                 size="large" | ||||
|                 data-testid={`VARIANT_EDIT_BUTTON_${variant.name}`} | ||||
|                 permission={UPDATE_FEATURE_VARIANTS} | ||||
|                 projectId={projectId} | ||||
|                 onClick={() => editVariant(variant.name)} | ||||
|             > | ||||
|                 <Edit /> | ||||
|             </PermissionIconButton> | ||||
|             <PermissionIconButton | ||||
|                 size="large" | ||||
|                 permission={UPDATE_FEATURE_VARIANTS} | ||||
|                 data-testid={`VARIANT_DELETE_BUTTON_${variant.name}`} | ||||
|                 projectId={projectId} | ||||
|                 onClick={() => | ||||
|                     setDelDialog({ | ||||
|                         show: true, | ||||
|                         name: variant.name, | ||||
|                     }) | ||||
|                 } | ||||
|             > | ||||
|                 <Delete /> | ||||
|             </PermissionIconButton> | ||||
|         </Box> | ||||
|     ); | ||||
| }; | ||||
| @ -1,100 +0,0 @@ | ||||
| .variantTable { | ||||
|     width: 100%; | ||||
|     max-width: 700px; | ||||
| 
 | ||||
|     th, | ||||
|     td { | ||||
|         text-align: center; | ||||
|     } | ||||
|     th:first-of-type, | ||||
|     td:first-of-type { | ||||
|         text-align: left; | ||||
|         width: 100%; | ||||
|     } | ||||
|     tbody tr:hover { | ||||
|         background-color: rgba(173, 216, 230, 0.2); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 600px) { | ||||
|     th.labels { | ||||
|         display: none; | ||||
|     } | ||||
|     td.labels { | ||||
|         display: none; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| th.labels { | ||||
|     text-align: right; | ||||
| } | ||||
| 
 | ||||
| td.labels { | ||||
|     text-align: right; | ||||
|     vertical-align: top; | ||||
| } | ||||
| 
 | ||||
| th.actions { | ||||
|     text-align: right; | ||||
| } | ||||
| 
 | ||||
| td.actions { | ||||
|     height: 100%; | ||||
|     text-align: right; | ||||
|     vertical-align: top; | ||||
| } | ||||
| 
 | ||||
| .actionsContainer { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
| } | ||||
| 
 | ||||
| .modal { | ||||
|     max-width: 90%; | ||||
|     width: 600px; | ||||
|     position: absolute !important; | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 600px) { | ||||
|     .modal { | ||||
|         top: 0 !important; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| .tooltip { | ||||
|     i { | ||||
|         font-size: 18px; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| .inputWeight { | ||||
|     text-align: right; | ||||
| } | ||||
| 
 | ||||
| .flexCenter { | ||||
|     display: flex; | ||||
|     justify-content: center; | ||||
|     align-items: center; | ||||
| } | ||||
| 
 | ||||
| .flex { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
| } | ||||
| 
 | ||||
| .marginL10 { | ||||
|     margin-left: 10px; | ||||
| } | ||||
| 
 | ||||
| .addVariantButton { | ||||
|     margin: 1rem 0; | ||||
| } | ||||
| 
 | ||||
| .paragraph { | ||||
|     max-width: 400px; | ||||
| } | ||||
| 
 | ||||
| .helperText { | ||||
|     display: block; | ||||
|     margin-top: 0.5rem; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user